From b41a5239c6de922f2cf2dd48f7f510541dffafc8 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 14 Feb 2020 15:51:51 +0300 Subject: [PATCH 01/11] core: always sort the result of GetValidators It wasn't sorted when all validators were elected. There is also no need to do `Unique()` on the result because validators are distinguished by the key, so no two registered validators can have the same key. --- pkg/core/blockchain.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index dce705a94..9d712f16a 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1409,20 +1409,20 @@ func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.P } uniqueSBValidators := standByValidators.Unique() - pubKeys := keys.PublicKeys{} + result := keys.PublicKeys{} for _, validator := range validators { if validator.RegisteredAndHasVotes() || uniqueSBValidators.Contains(validator.PublicKey) { - pubKeys = append(pubKeys, validator.PublicKey) + result = append(result, validator.PublicKey) } } - if pubKeys.Len() >= count { - return pubKeys[:count], nil - } - result := pubKeys.Unique() - for i := 0; i < uniqueSBValidators.Len() && result.Len() < count; i++ { - if !result.Contains(uniqueSBValidators[i]) { - result = append(result, uniqueSBValidators[i]) + if result.Len() >= count { + result = result[:count] + } else { + for i := 0; i < uniqueSBValidators.Len() && result.Len() < count; i++ { + if !result.Contains(uniqueSBValidators[i]) { + result = append(result, uniqueSBValidators[i]) + } } } sort.Sort(result) From a4294f4b5f916e70446785cbd0bac681b7ccdd68 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 14 Feb 2020 17:44:46 +0300 Subject: [PATCH 02/11] core: export UtilityTokenID and GoverningTokenID Both are very useful outside of the core, this change also makes respective transactions initialize with the package as they don't depend on any kind of input and it makes no sense recreating them again and again on every use. --- pkg/core/blockchain.go | 18 ++++++++--------- pkg/core/util.go | 45 +++++++++++++++++++++++++++--------------- pkg/core/util_test.go | 6 ++---- pkg/rpc/rpc.go | 6 ++---- 4 files changed, 42 insertions(+), 33 deletions(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 9d712f16a..1c3869c89 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -432,7 +432,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error { return err } - if prevTXOutput.AssetID.Equals(governingTokenTX().Hash()) { + if prevTXOutput.AssetID.Equals(GoverningTokenID()) { spentCoin := NewSpentCoinState(input.PrevHash, prevTXHeight) spentCoin.items[input.PrevIndex] = block.Index if err = cache.PutSpentCoinState(input.PrevHash, spentCoin); err != nil { @@ -648,14 +648,14 @@ func processOutputs(tx *transaction.Transaction, dao *cachedDao) error { } func processTXWithValidatorsAdd(output *transaction.Output, account *state.Account, dao *cachedDao) error { - if output.AssetID.Equals(governingTokenTX().Hash()) && len(account.Votes) > 0 { + if output.AssetID.Equals(GoverningTokenID()) && len(account.Votes) > 0 { return modAccountVotes(account, dao, output.Amount) } return nil } func processTXWithValidatorsSubtract(output *transaction.Output, account *state.Account, dao *cachedDao) error { - if output.AssetID.Equals(governingTokenTX().Hash()) && len(account.Votes) > 0 { + if output.AssetID.Equals(GoverningTokenID()) && len(account.Votes) > 0 { return modAccountVotes(account, dao, -output.Amount) } return nil @@ -724,7 +724,7 @@ func processAccountStateDescriptor(descriptor *transaction.StateDescriptor, dao } if descriptor.Field == "Votes" { - balance := account.GetBalanceValues()[governingTokenTX().Hash()] + balance := account.GetBalanceValues()[GoverningTokenID()] if err = modAccountVotes(account, dao, -balance); err != nil { return err } @@ -999,14 +999,14 @@ func (bc *Blockchain) FeePerByte(t *transaction.Transaction) util.Fixed8 { func (bc *Blockchain) NetworkFee(t *transaction.Transaction) util.Fixed8 { inputAmount := util.Fixed8FromInt64(0) for _, txOutput := range bc.References(t) { - if txOutput.AssetID == utilityTokenTX().Hash() { + if txOutput.AssetID == UtilityTokenID() { inputAmount.Add(txOutput.Amount) } } outputAmount := util.Fixed8FromInt64(0) for _, txOutput := range t.Outputs { - if txOutput.AssetID == utilityTokenTX().Hash() { + if txOutput.AssetID == UtilityTokenID() { outputAmount.Add(txOutput.Amount) } } @@ -1192,7 +1192,7 @@ func (bc *Blockchain) verifyResults(t *transaction.Transaction) error { if len(resultsDestroy) > 1 { return errors.New("tx has more than 1 destroy output") } - if len(resultsDestroy) == 1 && resultsDestroy[0].AssetID != utilityTokenTX().Hash() { + if len(resultsDestroy) == 1 && resultsDestroy[0].AssetID != UtilityTokenID() { return errors.New("tx destroys non-utility token") } sysfee := bc.SystemFee(t) @@ -1208,14 +1208,14 @@ func (bc *Blockchain) verifyResults(t *transaction.Transaction) error { switch t.Type { case transaction.MinerType, transaction.ClaimType: for _, r := range resultsIssue { - if r.AssetID != utilityTokenTX().Hash() { + if r.AssetID != UtilityTokenID() { return errors.New("miner or claim tx issues non-utility tokens") } } break case transaction.IssueType: for _, r := range resultsIssue { - if r.AssetID == utilityTokenTX().Hash() { + if r.AssetID == UtilityTokenID() { return errors.New("issue tx issues utility tokens") } } diff --git a/pkg/core/util.go b/pkg/core/util.go index 4465a0621..77fcddc22 100644 --- a/pkg/core/util.go +++ b/pkg/core/util.go @@ -13,6 +13,17 @@ import ( "github.com/CityOfZion/neo-go/pkg/vm/opcode" ) +var ( + // governingTokenTX represents transaction that is used to create + // governing (NEO) token. It's a part of the genesis block. + governingTokenTX transaction.Transaction + + // utilityTokenTX represents transaction that is used to create + // utility (GAS) token. It's a part of the genesis block. It's mostly + // useful for its hash that represents GAS asset ID. + utilityTokenTX transaction.Transaction +) + // createGenesisBlock creates a genesis block based on the given configuration. func createGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error) { validators, err := getValidators(cfg) @@ -38,8 +49,6 @@ func createGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error) }, } - governingTX := governingTokenTX() - utilityTX := utilityTokenTX() rawScript, err := smartcontract.CreateMultiSigRedeemScript( len(cfg.StandbyValidators)/2+1, validators, @@ -62,16 +71,16 @@ func createGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error) Outputs: []transaction.Output{}, Scripts: []transaction.Witness{}, }, - governingTX, - utilityTX, + &governingTokenTX, + &utilityTokenTX, { Type: transaction.IssueType, Data: &transaction.IssueTX{}, // no fields. Inputs: []transaction.Input{}, Outputs: []transaction.Output{ { - AssetID: governingTX.Hash(), - Amount: governingTX.Data.(*transaction.RegisterTX).Amount, + AssetID: governingTokenTX.Hash(), + Amount: governingTokenTX.Data.(*transaction.RegisterTX).Amount, ScriptHash: scriptOut, }, }, @@ -92,7 +101,7 @@ func createGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error) return b, nil } -func governingTokenTX() *transaction.Transaction { +func init() { admin := hash.Hash160([]byte{byte(opcode.PUSHT)}) registerTX := &transaction.RegisterTX{ AssetType: transaction.GoverningToken, @@ -102,7 +111,7 @@ func governingTokenTX() *transaction.Transaction { Admin: admin, } - tx := &transaction.Transaction{ + governingTokenTX = transaction.Transaction{ Type: transaction.RegisterType, Data: registerTX, Attributes: []transaction.Attribute{}, @@ -111,19 +120,15 @@ func governingTokenTX() *transaction.Transaction { Scripts: []transaction.Witness{}, } - return tx -} - -func utilityTokenTX() *transaction.Transaction { - admin := hash.Hash160([]byte{byte(opcode.PUSHF)}) - registerTX := &transaction.RegisterTX{ + admin = hash.Hash160([]byte{byte(opcode.PUSHF)}) + registerTX = &transaction.RegisterTX{ AssetType: transaction.UtilityToken, Name: "[{\"lang\":\"zh-CN\",\"name\":\"小蚁币\"},{\"lang\":\"en\",\"name\":\"AntCoin\"}]", Amount: calculateUtilityAmount(), Precision: 8, Admin: admin, } - tx := &transaction.Transaction{ + utilityTokenTX = transaction.Transaction{ Type: transaction.RegisterType, Data: registerTX, Attributes: []transaction.Attribute{}, @@ -131,8 +136,16 @@ func utilityTokenTX() *transaction.Transaction { Outputs: []transaction.Output{}, Scripts: []transaction.Witness{}, } +} - return tx +// GoverningTokenID returns the governing token (NEO) hash. +func GoverningTokenID() util.Uint256 { + return governingTokenTX.Hash() +} + +// UtilityTokenID returns the utility token (GAS) hash. +func UtilityTokenID() util.Uint256 { + return utilityTokenTX.Hash() } func getValidators(cfg config.ProtocolConfiguration) ([]*keys.PublicKey, error) { diff --git a/pkg/core/util_test.go b/pkg/core/util_test.go index b2222a56a..aaa39ae63 100644 --- a/pkg/core/util_test.go +++ b/pkg/core/util_test.go @@ -50,12 +50,10 @@ func TestGetConsensusAddressMainNet(t *testing.T) { func TestUtilityTokenTX(t *testing.T) { expect := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7" - tx := utilityTokenTX() - assert.Equal(t, expect, tx.Hash().StringLE()) + assert.Equal(t, expect, UtilityTokenID().StringLE()) } func TestGoverningTokenTX(t *testing.T) { expect := "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b" - tx := governingTokenTX() - assert.Equal(t, expect, tx.Hash().StringLE()) + assert.Equal(t, expect, GoverningTokenID().StringLE()) } diff --git a/pkg/rpc/rpc.go b/pkg/rpc/rpc.go index 9beaad3d3..8ba1af6c5 100644 --- a/pkg/rpc/rpc.go +++ b/pkg/rpc/rpc.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "fmt" + "github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/smartcontract" @@ -160,15 +161,12 @@ func (c *Client) SignAndPushInvocationTx(script []byte, wif *keys.WIF, gas util. var txHash util.Uint256 var err error - gasIDB, _ := hex.DecodeString("602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7") - gasID, _ := util.Uint256DecodeBytesLE(gasIDB) - tx := transaction.NewInvocationTX(script, gas) fromAddress := wif.PrivateKey.Address() if gas > 0 { - if err = AddInputsAndUnspentsToTx(tx, fromAddress, gasID, gas, c); err != nil { + if err = AddInputsAndUnspentsToTx(tx, fromAddress, core.UtilityTokenID(), gas, c); err != nil { return txHash, errors.Wrap(err, "failed to add inputs and unspents to transaction") } } From 17d1b1a2ae869cb1646fb0c00a73db763b0c33e4 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 14 Feb 2020 19:17:07 +0300 Subject: [PATCH 03/11] wallet: implement GetChangeAddress() To select the default address to send tokens to. --- pkg/wallet/testdata/wallet1.json | 1 + pkg/wallet/testdata/wallet2.json | 1 + pkg/wallet/wallet.go | 22 ++++++++++++++++++++++ pkg/wallet/wallet_test.go | 18 ++++++++++++++++++ 4 files changed, 42 insertions(+) create mode 100644 pkg/wallet/testdata/wallet1.json create mode 100644 pkg/wallet/testdata/wallet2.json diff --git a/pkg/wallet/testdata/wallet1.json b/pkg/wallet/testdata/wallet1.json new file mode 100644 index 000000000..d03726fd9 --- /dev/null +++ b/pkg/wallet/testdata/wallet1.json @@ -0,0 +1 @@ +{"name":"wallet1","version":"1.0","scrypt":{"n":16384,"r":8,"p":8},"accounts":[{"address":"AKkkumHbBipZ46UMZJoFynJMXzSRnBvKcs","label":null,"isDefault":false,"lock":false,"key":"6PYLmjBYJ4wQTCEfqvnznGJwZeW9pfUcV5m5oreHxqryUgqKpTRAFt9L8Y","contract":{"script":"2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2ac","parameters":[{"name":"parameter0","type":"Signature"}],"deployed":false},"extra":null},{"address":"AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU","label":null,"isDefault":false,"lock":false,"key":"6PYLmjBYJ4wQTCEfqvnznGJwZeW9pfUcV5m5oreHxqryUgqKpTRAFt9L8Y","contract":{"script":"532102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd622102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc22103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee69954ae","parameters":[{"name":"parameter0","type":"Signature"},{"name":"parameter1","type":"Signature"},{"name":"parameter2","type":"Signature"}],"deployed":false},"extra":null}],"extra":null} \ No newline at end of file diff --git a/pkg/wallet/testdata/wallet2.json b/pkg/wallet/testdata/wallet2.json new file mode 100644 index 000000000..926d0d688 --- /dev/null +++ b/pkg/wallet/testdata/wallet2.json @@ -0,0 +1 @@ +{"name":"wallet2","version":"1.0","scrypt":{"n":16384,"r":8,"p":8},"accounts":[{"address":"AKkkumHbBipZ46UMZJoFynJMXzSRnBvKcs","label":null,"isDefault":false,"lock":false,"key":"6PYLmjBYJ4wQTCEfqvnznGJwZeW9pfUcV5m5oreHxqryUgqKpTRAFt9L8Y","contract":{"script":"2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2ac","parameters":[{"name":"parameter0","type":"Signature"}],"deployed":false},"extra":null},{"address":"AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU","label":null,"isDefault":false,"lock":false,"key":"6PYXHjPaNvW8YknSXaKsTWjf9FRxo1s4naV2jdmSQEgzaqKGX368rndN3L","contract":{"script":"532102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd622102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc22103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee69954ae","parameters":[{"name":"parameter0","type":"Signature"},{"name":"parameter1","type":"Signature"},{"name":"parameter2","type":"Signature"}],"deployed":false},"extra":null},{"address":"AWLYWXB8C9Lt1nHdDZJnC5cpYJjgRDLk17","label":null,"isDefault":true,"lock":false,"key":"6PYXHjPaNvW8YknSXaKsTWjf9FRxo1s4naV2jdmSQEgzaqKGX368rndN3L","contract":{"script":"2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406eac","parameters":[{"name":"parameter0","type":"Signature"}],"deployed":false},"extra":null}],"extra":null} diff --git a/pkg/wallet/wallet.go b/pkg/wallet/wallet.go index c54aa20cc..fd9c9ab61 100644 --- a/pkg/wallet/wallet.go +++ b/pkg/wallet/wallet.go @@ -7,6 +7,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/util" + "github.com/CityOfZion/neo-go/pkg/vm" ) const ( @@ -129,3 +130,24 @@ func (w *Wallet) GetAccount(h util.Uint160) *Account { return nil } + +// GetChangeAddress returns the default address to send transaction's change to. +func (w *Wallet) GetChangeAddress() util.Uint160 { + var res util.Uint160 + var acc *Account + + for i := range w.Accounts { + if acc == nil || w.Accounts[i].Default { + if w.Accounts[i].Contract != nil && vm.IsSignatureContract(w.Accounts[i].Contract.Script) { + acc = w.Accounts[i] + if w.Accounts[i].Default { + break + } + } + } + } + if acc != nil { + res = acc.Contract.ScriptHash() + } + return res +} diff --git a/pkg/wallet/wallet_test.go b/pkg/wallet/wallet_test.go index dca5590da..c22972713 100644 --- a/pkg/wallet/wallet_test.go +++ b/pkg/wallet/wallet_test.go @@ -6,6 +6,7 @@ import ( "os" "testing" + "github.com/CityOfZion/neo-go/pkg/encoding/address" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -145,3 +146,20 @@ func TestWallet_GetAccount(t *testing.T) { assert.Equal(t, acc, wallet.GetAccount(h), "can't get %d account", i) } } + +func TestWalletGetChangeAddress(t *testing.T) { + w1, err := NewWalletFromFile("testdata/wallet1.json") + require.NoError(t, err) + sh := w1.GetChangeAddress() + // No default address, the first one is used. + expected, err := address.StringToUint160("AKkkumHbBipZ46UMZJoFynJMXzSRnBvKcs") + require.NoError(t, err) + require.Equal(t, expected, sh) + w2, err := NewWalletFromFile("testdata/wallet2.json") + require.NoError(t, err) + sh = w2.GetChangeAddress() + // Default address. + expected, err = address.StringToUint160("AWLYWXB8C9Lt1nHdDZJnC5cpYJjgRDLk17") + require.NoError(t, err) + require.Equal(t, expected, sh) +} From a2616cfafe45eda2c19d262fec0ee461d9705948 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 14 Feb 2020 19:21:24 +0300 Subject: [PATCH 04/11] consensus: correctly generate miner TX outputs based on net fee --- pkg/consensus/consensus.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/pkg/consensus/consensus.go b/pkg/consensus/consensus.go index d4daca7da..23c28ae88 100644 --- a/pkg/consensus/consensus.go +++ b/pkg/consensus/consensus.go @@ -411,10 +411,25 @@ func (s *service) getVerifiedTx(count int) []block.Transaction { } res := make([]block.Transaction, len(txx)+1) + var netFee util.Fixed8 for i := range txx { res[i+1] = txx[i] + netFee += s.Config.Chain.NetworkFee(txx[i]) } + var txOuts []transaction.Output + if netFee != 0 { + sh := s.wallet.GetChangeAddress() + if sh.Equals(util.Uint160{}) { + pk := s.dbft.Pub.(*publicKey) + sh = hash.Hash160(pk.GetVerificationScript()) + } + txOuts = []transaction.Output{transaction.Output{ + AssetID: core.UtilityTokenID(), + Amount: netFee, + ScriptHash: sh, + }} + } for { nonce := rand.Uint32() res[0] = &transaction.Transaction{ @@ -423,7 +438,7 @@ func (s *service) getVerifiedTx(count int) []block.Transaction { Data: &transaction.MinerTX{Nonce: nonce}, Attributes: nil, Inputs: nil, - Outputs: nil, + Outputs: txOuts, Scripts: nil, Trimmed: false, } From c5d54e9992034f2d1518f334c7bc4843393206e4 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 14 Feb 2020 20:46:05 +0300 Subject: [PATCH 05/11] network: introduce (*Server).IsInSync, start consensus in synced state We define synchronized state as a combination of minimum number of peers and chain height being not behind of more than 2/3 of these peers. --- pkg/network/server.go | 66 +++++++++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 15 deletions(-) diff --git a/pkg/network/server.go b/pkg/network/server.go index 5a45ed393..f238df365 100644 --- a/pkg/network/server.go +++ b/pkg/network/server.go @@ -64,7 +64,7 @@ type ( unregister chan peerDrop quit chan struct{} - connected *atomic.Bool + consensusStarted *atomic.Bool log *zap.Logger } @@ -88,17 +88,20 @@ func NewServer(config ServerConfig, chain core.Blockchainer, log *zap.Logger) (* } s := &Server{ - ServerConfig: config, - chain: chain, - id: randomID(), - quit: make(chan struct{}), - register: make(chan Peer), - unregister: make(chan peerDrop), - peers: make(map[Peer]bool), - connected: atomic.NewBool(false), - log: log, + ServerConfig: config, + chain: chain, + id: randomID(), + quit: make(chan struct{}), + register: make(chan Peer), + unregister: make(chan peerDrop), + peers: make(map[Peer]bool), + consensusStarted: atomic.NewBool(false), + log: log, } - s.bQueue = newBlockQueue(maxBlockBatch, chain, log, s.relayBlock) + s.bQueue = newBlockQueue(maxBlockBatch, chain, log, func(b *block.Block) { + s.tryStartConsensus() + s.relayBlock(b) + }) srv, err := consensus.NewService(consensus.Config{ Logger: log, @@ -274,13 +277,13 @@ func (s *Server) runProto() { } func (s *Server) tryStartConsensus() { - if s.Wallet == nil || s.connected.Load() { + if s.Wallet == nil || s.consensusStarted.Load() { return } - if s.HandshakedPeersCount() >= s.MinPeers { - s.log.Info("minimum amount of peers were connected to") - if s.connected.CAS(false, true) { + if s.IsInSync() { + s.log.Info("node reached synchronized state, starting consensus") + if s.consensusStarted.CAS(false, true) { s.consensus.Start() } } @@ -336,6 +339,39 @@ func (s *Server) getVersionMsg() *Message { return s.MkMsg(CMDVersion, payload) } +// IsInSync answers the question of whether the server is in sync with the +// network or not (at least how the server itself sees it). The server operates +// with the data that it has, the number of peers (that has to be more than +// minimum number) and height of these peers (our chain has to be not lower +// than 2/3 of our peers have). Ideally we would check for the highest of the +// peers, but the problem is that they can lie to us and send whatever height +// they want to. +func (s *Server) IsInSync() bool { + var peersNumber int + var notHigher int + + if s.MinPeers == 0 { + return true + } + + ourLastBlock := s.chain.BlockHeight() + + s.lock.RLock() + for p := range s.peers { + if p.Handshaked() { + peersNumber++ + if ourLastBlock >= p.LastBlockIndex() { + notHigher++ + } + } + } + s.lock.RUnlock() + + // Checking bQueue would also be nice, but it can be filled with garbage + // easily at the moment. + return peersNumber >= s.MinPeers && (3*notHigher > 2*peersNumber) // && s.bQueue.length() == 0 +} + // When a peer sends out his version we reply with verack after validating // the version. func (s *Server) handleVersionCmd(p Peer, version *payload.Version) error { From 37c48b00b44e43c6884fb2d19069dbb747204932 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 17 Feb 2020 16:20:04 +0300 Subject: [PATCH 06/11] consensus/network: reinit dbft after block addition Don't stall on some height if everyone else have moved up already. Fix #673. --- pkg/consensus/consensus.go | 35 +++++++++++++++++++++++++++-------- pkg/network/server.go | 6 +++++- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/pkg/consensus/consensus.go b/pkg/consensus/consensus.go index 23c28ae88..b7722501d 100644 --- a/pkg/consensus/consensus.go +++ b/pkg/consensus/consensus.go @@ -42,6 +42,9 @@ type Service interface { OnTransaction(tx *transaction.Transaction) // GetPayload returns Payload with specified hash if it is present in the local cache. GetPayload(h util.Uint256) *Payload + // OnNewBlock notifies consensus service that there is a new block in + // the chain (without explicitly passing it to the service). + OnNewBlock() } type service struct { @@ -57,6 +60,9 @@ type service struct { // everything in single thread. messages chan Payload transactions chan *transaction.Transaction + // blockEvents is used to pass a new block event to the consensus + // process. + blockEvents chan struct{} lastProposal []util.Uint256 wallet *wallet.Wallet } @@ -101,6 +107,7 @@ func NewService(cfg Config) (Service, error) { messages: make(chan Payload, 100), transactions: make(chan *transaction.Transaction, 100), + blockEvents: make(chan struct{}, 1), } if cfg.Wallet == nil { @@ -168,14 +175,7 @@ func (s *service) eventLoop() { s.log.Debug("timer fired", zap.Uint32("height", hv.Height), zap.Uint("view", uint(hv.View))) - if s.Chain.BlockHeight() >= s.dbft.BlockIndex { - s.log.Debug("chain already advanced", - zap.Uint32("dbft index", s.dbft.BlockIndex), - zap.Uint32("chain index", s.Chain.BlockHeight())) - s.dbft.InitializeConsensus(0) - } else { - s.dbft.OnTimeout(hv) - } + s.dbft.OnTimeout(hv) case msg := <-s.messages: fields := []zap.Field{ zap.Uint16("from", msg.validatorIndex), @@ -204,6 +204,11 @@ func (s *service) eventLoop() { s.dbft.OnReceive(&msg) case tx := <-s.transactions: s.dbft.OnTransaction(tx) + case <-s.blockEvents: + s.log.Debug("new block in the chain", + zap.Uint32("dbft index", s.dbft.BlockIndex), + zap.Uint32("chain index", s.Chain.BlockHeight())) + s.dbft.InitializeConsensus(0) } } } @@ -276,6 +281,20 @@ func (s *service) OnTransaction(tx *transaction.Transaction) { } } +// OnNewBlock notifies consensus process that there is a new block in the chain +// and dbft should probably be reinitialized. +func (s *service) OnNewBlock() { + if s.dbft != nil { + // If there is something in the queue already, the second + // consecutive event doesn't make much sense (reinitializing + // dbft twice doesn't improve it in any way). + select { + case s.blockEvents <- struct{}{}: + default: + } + } +} + // GetPayload returns payload stored in cache. func (s *service) GetPayload(h util.Uint256) *Payload { p := s.cache.Get(h) diff --git a/pkg/network/server.go b/pkg/network/server.go index f238df365..0c8bdee4b 100644 --- a/pkg/network/server.go +++ b/pkg/network/server.go @@ -99,7 +99,11 @@ func NewServer(config ServerConfig, chain core.Blockchainer, log *zap.Logger) (* log: log, } s.bQueue = newBlockQueue(maxBlockBatch, chain, log, func(b *block.Block) { - s.tryStartConsensus() + if s.consensusStarted.Load() { + s.consensus.OnNewBlock() + } else { + s.tryStartConsensus() + } s.relayBlock(b) }) From 06daeb44f301ab1b778999d430e300677c205315 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 18 Feb 2020 18:42:11 +0300 Subject: [PATCH 07/11] core: make IsLowPriority work with pre-calculated fee Don't recalculate it again and again. --- pkg/consensus/consensus_test.go | 2 +- pkg/core/blockchain.go | 6 +++--- pkg/core/mempool/feer.go | 2 +- pkg/core/mempool/mem_pool.go | 2 +- pkg/core/mempool/mem_pool_test.go | 2 +- pkg/network/helper_test.go | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/consensus/consensus_test.go b/pkg/consensus/consensus_test.go index db31c204d..7243bce01 100644 --- a/pkg/consensus/consensus_test.go +++ b/pkg/consensus/consensus_test.go @@ -239,6 +239,6 @@ func newTestChain(t *testing.T) *core.Blockchain { type feer struct{} func (fs *feer) NetworkFee(*transaction.Transaction) util.Fixed8 { return util.Fixed8(0) } -func (fs *feer) IsLowPriority(*transaction.Transaction) bool { return false } +func (fs *feer) IsLowPriority(util.Fixed8) bool { return false } func (fs *feer) FeePerByte(*transaction.Transaction) util.Fixed8 { return util.Fixed8(0) } func (fs *feer) SystemFee(*transaction.Transaction) util.Fixed8 { return util.Fixed8(0) } diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 1c3869c89..2aa323562 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1019,10 +1019,10 @@ func (bc *Blockchain) SystemFee(t *transaction.Transaction) util.Fixed8 { return bc.GetConfig().SystemFee.TryGetValue(t.Type) } -// IsLowPriority flags a transaction as low priority if the network fee is less than +// IsLowPriority checks given fee for being less than configured // LowPriorityThreshold. -func (bc *Blockchain) IsLowPriority(t *transaction.Transaction) bool { - return bc.NetworkFee(t) < util.Fixed8FromFloat(bc.GetConfig().LowPriorityThreshold) +func (bc *Blockchain) IsLowPriority(fee util.Fixed8) bool { + return fee < util.Fixed8FromFloat(bc.GetConfig().LowPriorityThreshold) } // GetMemPool returns the memory pool of the blockchain. diff --git a/pkg/core/mempool/feer.go b/pkg/core/mempool/feer.go index f92c0e1d1..74b3fb036 100644 --- a/pkg/core/mempool/feer.go +++ b/pkg/core/mempool/feer.go @@ -8,7 +8,7 @@ import ( // Feer is an interface that abstract the implementation of the fee calculation. type Feer interface { NetworkFee(t *transaction.Transaction) util.Fixed8 - IsLowPriority(t *transaction.Transaction) bool + IsLowPriority(util.Fixed8) bool FeePerByte(t *transaction.Transaction) util.Fixed8 SystemFee(t *transaction.Transaction) util.Fixed8 } diff --git a/pkg/core/mempool/mem_pool.go b/pkg/core/mempool/mem_pool.go index a2f3ef9af..3facd377a 100644 --- a/pkg/core/mempool/mem_pool.go +++ b/pkg/core/mempool/mem_pool.go @@ -128,8 +128,8 @@ func (mp *Pool) Add(t *transaction.Transaction, fee Feer) error { timeStamp: time.Now().UTC(), perByteFee: fee.FeePerByte(t), netFee: fee.NetworkFee(t), - isLowPrio: fee.IsLowPriority(t), } + pItem.isLowPrio = fee.IsLowPriority(pItem.netFee) mp.lock.Lock() if !mp.verifyInputs(t) { mp.lock.Unlock() diff --git a/pkg/core/mempool/mem_pool_test.go b/pkg/core/mempool/mem_pool_test.go index 4cd529a06..f60829a2a 100644 --- a/pkg/core/mempool/mem_pool_test.go +++ b/pkg/core/mempool/mem_pool_test.go @@ -22,7 +22,7 @@ func (fs *FeerStub) NetworkFee(*transaction.Transaction) util.Fixed8 { return fs.netFee } -func (fs *FeerStub) IsLowPriority(*transaction.Transaction) bool { +func (fs *FeerStub) IsLowPriority(util.Fixed8) bool { return fs.lowPriority } diff --git a/pkg/network/helper_test.go b/pkg/network/helper_test.go index 1ab191d87..468819736 100644 --- a/pkg/network/helper_test.go +++ b/pkg/network/helper_test.go @@ -122,7 +122,7 @@ func (chain testChain) GetMemPool() *mempool.Pool { panic("TODO") } -func (chain testChain) IsLowPriority(*transaction.Transaction) bool { +func (chain testChain) IsLowPriority(util.Fixed8) bool { panic("TODO") } From 22f56675305ec145c00515476cf6d67e029ad6a0 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 18 Feb 2020 18:56:41 +0300 Subject: [PATCH 08/11] mempool: return fee along with tx when requesting tx Users of GetVerifiedTransactions() don't want to recalculate tx fee and it's nice to have it returned from TryGetValue() also sometimes. --- pkg/consensus/consensus.go | 13 +++++++------ pkg/core/blockchain.go | 2 +- pkg/core/mempool/mem_pool.go | 25 +++++++++++++++---------- pkg/core/mempool/mem_pool_test.go | 16 ++++++++-------- 4 files changed, 31 insertions(+), 25 deletions(-) diff --git a/pkg/consensus/consensus.go b/pkg/consensus/consensus.go index b7722501d..fc9bc88c8 100644 --- a/pkg/consensus/consensus.go +++ b/pkg/consensus/consensus.go @@ -9,6 +9,7 @@ import ( "github.com/CityOfZion/neo-go/config" "github.com/CityOfZion/neo-go/pkg/core" coreb "github.com/CityOfZion/neo-go/pkg/core/block" + "github.com/CityOfZion/neo-go/pkg/core/mempool" "github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/crypto/hash" "github.com/CityOfZion/neo-go/pkg/crypto/keys" @@ -412,13 +413,13 @@ func (s *service) getBlock(h util.Uint256) block.Block { func (s *service) getVerifiedTx(count int) []block.Transaction { pool := s.Config.Chain.GetMemPool() - var txx []*transaction.Transaction + var txx []mempool.TxWithFee if s.dbft.ViewNumber > 0 { - txx = make([]*transaction.Transaction, 0, len(s.lastProposal)) + txx = make([]mempool.TxWithFee, 0, len(s.lastProposal)) for i := range s.lastProposal { - if tx, ok := pool.TryGetValue(s.lastProposal[i]); ok { - txx = append(txx, tx) + if tx, fee, ok := pool.TryGetValue(s.lastProposal[i]); ok { + txx = append(txx, mempool.TxWithFee{Tx: tx, Fee: fee}) } } @@ -432,8 +433,8 @@ func (s *service) getVerifiedTx(count int) []block.Transaction { res := make([]block.Transaction, len(txx)+1) var netFee util.Fixed8 for i := range txx { - res[i+1] = txx[i] - netFee += s.Config.Chain.NetworkFee(txx[i]) + res[i+1] = txx[i].Tx + netFee += txx[i].Fee } var txOuts []transaction.Output diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 2aa323562..5199c643d 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -814,7 +814,7 @@ func (bc *Blockchain) headerListLen() (n int) { // GetTransaction returns a TX and its height by the given hash. func (bc *Blockchain) GetTransaction(hash util.Uint256) (*transaction.Transaction, uint32, error) { - if tx, ok := bc.memPool.TryGetValue(hash); ok { + if tx, _, ok := bc.memPool.TryGetValue(hash); ok { return tx, 0, nil // the height is not actually defined for memPool transaction. Not sure if zero is a good number in this case. } return bc.dao.GetTransaction(hash) diff --git a/pkg/core/mempool/mem_pool.go b/pkg/core/mempool/mem_pool.go index 3facd377a..9a99a1774 100644 --- a/pkg/core/mempool/mem_pool.go +++ b/pkg/core/mempool/mem_pool.go @@ -35,6 +35,12 @@ type item struct { // items is a slice of item. type items []*item +// TxWithFee combines transaction and its precalculated network fee. +type TxWithFee struct { + Tx *transaction.Transaction + Fee util.Fixed8 +} + // Pool stores the unconfirms transactions. type Pool struct { lock sync.RWMutex @@ -224,29 +230,28 @@ func NewMemPool(capacity int) Pool { } } -// TryGetValue returns a transaction if it exists in the memory pool. -func (mp *Pool) TryGetValue(hash util.Uint256) (*transaction.Transaction, bool) { +// TryGetValue returns a transaction and its fee if it exists in the memory pool. +func (mp *Pool) TryGetValue(hash util.Uint256) (*transaction.Transaction, util.Fixed8, bool) { mp.lock.RLock() defer mp.lock.RUnlock() if pItem, ok := mp.verifiedMap[hash]; ok { - return pItem.txn, ok + return pItem.txn, pItem.netFee, ok } - return nil, false + return nil, 0, false } // GetVerifiedTransactions returns a slice of Input from all the transactions in the memory pool // whose hash is not included in excludedHashes. -func (mp *Pool) GetVerifiedTransactions() []*transaction.Transaction { +func (mp *Pool) GetVerifiedTransactions() []TxWithFee { mp.lock.RLock() defer mp.lock.RUnlock() - var t = make([]*transaction.Transaction, len(mp.verifiedTxes)) - var i int + var t = make([]TxWithFee, len(mp.verifiedTxes)) - for _, p := range mp.verifiedTxes { - t[i] = p.txn - i++ + for i := range mp.verifiedTxes { + t[i].Tx = mp.verifiedTxes[i].txn + t[i].Fee = mp.verifiedTxes[i].netFee } return t diff --git a/pkg/core/mempool/mem_pool_test.go b/pkg/core/mempool/mem_pool_test.go index f60829a2a..b6a9401be 100644 --- a/pkg/core/mempool/mem_pool_test.go +++ b/pkg/core/mempool/mem_pool_test.go @@ -37,16 +37,16 @@ func (fs *FeerStub) SystemFee(*transaction.Transaction) util.Fixed8 { func testMemPoolAddRemoveWithFeer(t *testing.T, fs Feer) { mp := NewMemPool(10) tx := newMinerTX(0) - _, ok := mp.TryGetValue(tx.Hash()) + _, _, ok := mp.TryGetValue(tx.Hash()) require.Equal(t, false, ok) require.NoError(t, mp.Add(tx, fs)) // Re-adding should fail. require.Error(t, mp.Add(tx, fs)) - tx2, ok := mp.TryGetValue(tx.Hash()) + tx2, _, ok := mp.TryGetValue(tx.Hash()) require.Equal(t, true, ok) require.Equal(t, tx, tx2) mp.Remove(tx.Hash()) - _, ok = mp.TryGetValue(tx.Hash()) + _, _, ok = mp.TryGetValue(tx.Hash()) require.Equal(t, false, ok) // Make sure nothing left in the mempool after removal. assert.Equal(t, 0, len(mp.verifiedMap)) @@ -173,8 +173,8 @@ func TestGetVerified(t *testing.T) { require.Equal(t, mempoolSize, mp.Count()) verTxes := mp.GetVerifiedTransactions() require.Equal(t, mempoolSize, len(verTxes)) - for _, tx := range verTxes { - require.Contains(t, txes, tx) + for _, txf := range verTxes { + require.Contains(t, txes, txf.Tx) } for _, tx := range txes { mp.Remove(tx.Hash()) @@ -210,8 +210,8 @@ func TestRemoveStale(t *testing.T) { }) require.Equal(t, mempoolSize/2, mp.Count()) verTxes := mp.GetVerifiedTransactions() - for _, tx := range verTxes { - require.NotContains(t, txes1, tx) - require.Contains(t, txes2, tx) + for _, txf := range verTxes { + require.NotContains(t, txes1, txf.Tx) + require.Contains(t, txes2, txf.Tx) } } From eb11e5fb11ec98464059f2594e8babc3698a2fcc Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 18 Feb 2020 20:16:38 +0300 Subject: [PATCH 09/11] 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 } From 4299d1cf1b13410391d3f5689c3970f0415bcbc2 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 18 Feb 2020 20:31:45 +0300 Subject: [PATCH 10/11] update dbft package with nspcc-dev/dbft#25 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6f2ea2e94..0cf506aed 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/go-redis/redis v6.10.2+incompatible github.com/go-yaml/yaml v2.1.0+incompatible github.com/mr-tron/base58 v1.1.2 - github.com/nspcc-dev/dbft v0.0.0-20200211143830-4deeb124d7f9 + github.com/nspcc-dev/dbft v0.0.0-20200218131838-be55fd41ea78 github.com/nspcc-dev/rfc6979 v0.2.0 github.com/pkg/errors v0.8.1 github.com/prometheus/client_golang v1.2.1 diff --git a/go.sum b/go.sum index dfa063289..90995b721 100644 --- a/go.sum +++ b/go.sum @@ -97,8 +97,8 @@ github.com/nspcc-dev/dbft v0.0.0-20191209120240-0d6b7568d9ae h1:T5V1QANlNMKun0EP github.com/nspcc-dev/dbft v0.0.0-20191209120240-0d6b7568d9ae/go.mod h1:3FjXOoHmA51EGfb5GS/HOv7VdmngNRTssSeQ729dvGY= github.com/nspcc-dev/dbft v0.0.0-20200117124306-478e5cfbf03a h1:ajvxgEe9qY4vvoSmrADqdDx7hReodKTnT2IXN++qZG8= github.com/nspcc-dev/dbft v0.0.0-20200117124306-478e5cfbf03a/go.mod h1:/YFK+XOxxg0Bfm6P92lY5eDSLYfp06XOdL8KAVgXjVk= -github.com/nspcc-dev/dbft v0.0.0-20200211143830-4deeb124d7f9 h1:P3uOj+M3DkUlwGEFHbmwRHUZdpCuFzfM30j6iBjrPbY= -github.com/nspcc-dev/dbft v0.0.0-20200211143830-4deeb124d7f9/go.mod h1:O0qtn62prQSqizzoagHmuuKoz8QMkU3SzBoKdEvm3aQ= +github.com/nspcc-dev/dbft v0.0.0-20200218131838-be55fd41ea78 h1:cb8hWea3yyqbxzmnqqSSgqVCRoKNrFaS99Cqt0Qm7nQ= +github.com/nspcc-dev/dbft v0.0.0-20200218131838-be55fd41ea78/go.mod h1:O0qtn62prQSqizzoagHmuuKoz8QMkU3SzBoKdEvm3aQ= github.com/nspcc-dev/neofs-crypto v0.2.0 h1:ftN+59WqxSWz/RCgXYOfhmltOOqU+udsNQSvN6wkFck= github.com/nspcc-dev/neofs-crypto v0.2.0/go.mod h1:F/96fUzPM3wR+UGsPi3faVNmFlA9KAEAUQR7dMxZmNA= github.com/nspcc-dev/neofs-crypto v0.2.3 h1:aca3X2aly92ENRbFK+kH6Hd+J9EQ4Eu6XMVoITSIKtc= From a903147b60b3c1068b44e9485346463759d178e9 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Wed, 19 Feb 2020 12:10:36 +0300 Subject: [PATCH 11/11] keys: rename Signature to GetScriptHash, make it return Uint160 Signature itself wasn't used at all and its name is very misleading, Uint160 script hash is way more useful. --- pkg/consensus/consensus.go | 10 ++++------ pkg/core/interop_system.go | 3 +-- pkg/crypto/keys/private_key.go | 8 +++++--- pkg/crypto/keys/publickey.go | 13 +++++-------- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/pkg/consensus/consensus.go b/pkg/consensus/consensus.go index 463d99ef8..fdb57dfc6 100644 --- a/pkg/consensus/consensus.go +++ b/pkg/consensus/consensus.go @@ -11,7 +11,6 @@ import ( coreb "github.com/CityOfZion/neo-go/pkg/core/block" "github.com/CityOfZion/neo-go/pkg/core/mempool" "github.com/CityOfZion/neo-go/pkg/core/transaction" - "github.com/CityOfZion/neo-go/pkg/crypto/hash" "github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/smartcontract" "github.com/CityOfZion/neo-go/pkg/util" @@ -221,16 +220,15 @@ func (s *service) validatePayload(p *Payload) bool { } pub := validators[p.validatorIndex] - vs := pub.(*publicKey).GetVerificationScript() - h := hash.Hash160(vs) + h := pub.(*publicKey).GetScriptHash() return p.Verify(h) } func (s *service) getKeyPair(pubs []crypto.PublicKey) (int, crypto.PrivateKey, crypto.PublicKey) { for i := range pubs { - script := pubs[i].(*publicKey).GetVerificationScript() - acc := s.wallet.GetAccount(hash.Hash160(script)) + sh := pubs[i].(*publicKey).GetScriptHash() + acc := s.wallet.GetAccount(sh) if acc == nil { continue } @@ -446,7 +444,7 @@ func (s *service) getVerifiedTx(count int) []block.Transaction { sh := s.wallet.GetChangeAddress() if sh.Equals(util.Uint160{}) { pk := s.dbft.Pub.(*publicKey) - sh = hash.Hash160(pk.GetVerificationScript()) + sh = pk.GetScriptHash() } txOuts = []transaction.Output{transaction.Output{ AssetID: core.UtilityTokenID(), diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index 811f036ae..7f00f51fe 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -8,7 +8,6 @@ import ( "github.com/CityOfZion/neo-go/pkg/core/block" "github.com/CityOfZion/neo-go/pkg/core/state" "github.com/CityOfZion/neo-go/pkg/core/transaction" - "github.com/CityOfZion/neo-go/pkg/crypto/hash" "github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/smartcontract/trigger" "github.com/CityOfZion/neo-go/pkg/util" @@ -311,7 +310,7 @@ func (ic *interopContext) checkHashedWitness(hash util.Uint160) (bool, error) { // checkKeyedWitness checks hash of signature check contract with a given public // key against current list of script hashes for verifying in the interop context. func (ic *interopContext) checkKeyedWitness(key *keys.PublicKey) (bool, error) { - return ic.checkHashedWitness(hash.Hash160(key.GetVerificationScript())) + return ic.checkHashedWitness(key.GetScriptHash()) } // runtimeCheckWitness checks witnesses. diff --git a/pkg/crypto/keys/private_key.go b/pkg/crypto/keys/private_key.go index c3aef6663..bf99021d4 100644 --- a/pkg/crypto/keys/private_key.go +++ b/pkg/crypto/keys/private_key.go @@ -10,6 +10,7 @@ import ( "fmt" "math/big" + "github.com/CityOfZion/neo-go/pkg/util" "github.com/nspcc-dev/rfc6979" ) @@ -98,10 +99,11 @@ func (p *PrivateKey) Address() string { return pk.Address() } -// Signature creates the signature using the private key. -func (p *PrivateKey) Signature() []byte { +// GetScriptHash returns verification script hash for public key associated with +// the private key. +func (p *PrivateKey) GetScriptHash() util.Uint160 { pk := p.PublicKey() - return pk.Signature() + return pk.GetScriptHash() } // Sign signs arbitrary length data using the private key. diff --git a/pkg/crypto/keys/publickey.go b/pkg/crypto/keys/publickey.go index b22e2f9f3..60951851e 100644 --- a/pkg/crypto/keys/publickey.go +++ b/pkg/crypto/keys/publickey.go @@ -12,6 +12,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/crypto/hash" "github.com/CityOfZion/neo-go/pkg/encoding/address" "github.com/CityOfZion/neo-go/pkg/io" + "github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/vm/opcode" "github.com/pkg/errors" ) @@ -233,18 +234,14 @@ func (p *PublicKey) GetVerificationScript() []byte { return b } -// Signature returns a NEO-specific hash of the key. -func (p *PublicKey) Signature() []byte { - sig := hash.Hash160(p.GetVerificationScript()) - - return sig.BytesBE() +// GetScriptHash returns a Hash160 of verification script for the key. +func (p *PublicKey) GetScriptHash() util.Uint160 { + return hash.Hash160(p.GetVerificationScript()) } // Address returns a base58-encoded NEO-specific address based on the key hash. func (p *PublicKey) Address() string { - sig := hash.Hash160(p.GetVerificationScript()) - - return address.Uint160ToString(sig) + return address.Uint160ToString(p.GetScriptHash()) } // Verify returns true if the signature is valid and corresponds