diff --git a/pkg/core/native/contract.go b/pkg/core/native/contract.go index 95faa2467..13f8ae0d9 100644 --- a/pkg/core/native/contract.go +++ b/pkg/core/native/contract.go @@ -77,19 +77,17 @@ func NewContracts(cfg config.ProtocolConfiguration) *Contracts { gas := newGAS(int64(cfg.InitialGASSupply), cfg.P2PSigExtensions) neo := newNEO() + policy := newPolicy() neo.GAS = gas + neo.Policy = policy gas.NEO = neo mgmt.NEO = neo + policy.NEO = neo cs.GAS = gas cs.NEO = neo - cs.Contracts = append(cs.Contracts, neo) - cs.Contracts = append(cs.Contracts, gas) - - policy := newPolicy() - policy.NEO = neo cs.Policy = policy - cs.Contracts = append(cs.Contracts, policy) + cs.Contracts = append(cs.Contracts, neo, gas, policy) desig := newDesignate(cfg.P2PSigExtensions) desig.NEO = neo diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index c3ee86eac..715c7f140 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -20,17 +20,20 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" + "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) // NEO represents NEO native contract. type NEO struct { nep17TokenNative - GAS *GAS + GAS *GAS + Policy *Policy // gasPerBlock represents current value of generated gas per block. // It is append-only and doesn't need to be copied when used. @@ -822,11 +825,14 @@ func (n *NEO) ModifyAccountVotes(acc *state.NEOBalance, d *dao.Simple, value *bi func (n *NEO) getCandidates(d *dao.Simple, sortByKey bool) ([]keyWithVotes, error) { arr := make([]keyWithVotes, 0) + buf := io.NewBufBinWriter() d.Seek(n.ID, storage.SeekRange{Prefix: []byte{prefixCandidate}}, func(k, v []byte) bool { c := new(candidate).FromBytes(v) - if c.Registered { + emit.CheckSig(buf.BinWriter, k) + if c.Registered && !n.Policy.IsBlockedInternal(d, hash.Hash160(buf.Bytes())) { arr = append(arr, keyWithVotes{Key: string(k), Votes: &c.Votes}) } + buf.Reset() return true }) diff --git a/pkg/core/native/native_test/neo_test.go b/pkg/core/native/native_test/neo_test.go index d31dd4bab..3ade90aa4 100644 --- a/pkg/core/native/native_test/neo_test.go +++ b/pkg/core/native/native_test/neo_test.go @@ -49,6 +49,7 @@ func TestNEO_RegisterPrice(t *testing.T) { func TestNEO_Vote(t *testing.T) { neoCommitteeInvoker := newNeoCommitteeClient(t, 100_0000_0000) neoValidatorsInvoker := neoCommitteeInvoker.WithSigners(neoCommitteeInvoker.Validator) + policyInvoker := neoCommitteeInvoker.CommitteeInvoker(neoCommitteeInvoker.NativeHash(t, nativenames.Policy)) e := neoCommitteeInvoker.Executor cfg := e.Chain.GetConfig() @@ -71,23 +72,23 @@ func TestNEO_Vote(t *testing.T) { // voters vote for candidates. The aim of this test is to check that voting // reward is proportional to the NEO balance. - voters := make([]neotest.Signer, committeeSize) + voters := make([]neotest.Signer, committeeSize+1) // referenceAccounts perform the same actions as voters except voting, i.e. we // will transfer the same amount of NEO to referenceAccounts and see how much // GAS they receive for NEO ownership. We need these values to be able to define // how much GAS voters receive for NEO ownership. - referenceAccounts := make([]neotest.Signer, committeeSize) - candidates := make([]neotest.Signer, committeeSize) - for i := 0; i < committeeSize; i++ { + referenceAccounts := make([]neotest.Signer, committeeSize+1) + candidates := make([]neotest.Signer, committeeSize+1) + for i := 0; i < committeeSize+1; i++ { voters[i] = e.NewAccount(t, 10_0000_0000) referenceAccounts[i] = e.NewAccount(t, 10_0000_0000) candidates[i] = e.NewAccount(t, 2000_0000_0000) // enough for one registration } txes := make([]*transaction.Transaction, 0, committeeSize*4-2) - for i := 0; i < committeeSize; i++ { - transferTx := neoValidatorsInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), voters[i].(neotest.SingleSigner).Account().PrivateKey().GetScriptHash(), int64(committeeSize-i)*1000000, nil) + for i := 0; i < committeeSize+1; i++ { + transferTx := neoValidatorsInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), voters[i].(neotest.SingleSigner).Account().PrivateKey().GetScriptHash(), int64(committeeSize+1-i)*1000000, nil) txes = append(txes, transferTx) - transferTx = neoValidatorsInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), referenceAccounts[i].(neotest.SingleSigner).Account().PrivateKey().GetScriptHash(), int64(committeeSize-i)*1000000, nil) + transferTx = neoValidatorsInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), referenceAccounts[i].(neotest.SingleSigner).Account().PrivateKey().GetScriptHash(), int64(committeeSize+1-i)*1000000, nil) txes = append(txes, transferTx) if i > 0 { registerTx := neoValidatorsInvoker.WithSigners(candidates[i]).PrepareInvoke(t, "registerCandidate", candidates[i].(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes()) @@ -96,6 +97,7 @@ func TestNEO_Vote(t *testing.T) { txes = append(txes, voteTx) } } + txes = append(txes, policyInvoker.PrepareInvoke(t, "blockAccount", candidates[len(candidates)-1].(neotest.SingleSigner).Account().PrivateKey().PublicKey().GetScriptHash())) neoValidatorsInvoker.AddNewBlock(t, txes...) for _, tx := range txes { e.CheckHalt(t, tx.Hash(), stackitem.Make(true)) // luckily, both `transfer`, `registerCandidate` and `vote` return boolean values @@ -137,12 +139,12 @@ func TestNEO_Vote(t *testing.T) { require.EqualValues(t, sortedCandidates, pubs) t.Run("check voter rewards", func(t *testing.T) { - gasBalance := make([]*big.Int, len(voters)) - referenceGASBalance := make([]*big.Int, len(referenceAccounts)) - neoBalance := make([]*big.Int, len(voters)) - txes = make([]*transaction.Transaction, 0, len(voters)) + gasBalance := make([]*big.Int, len(voters)-1) + referenceGASBalance := make([]*big.Int, len(referenceAccounts)-1) + neoBalance := make([]*big.Int, len(voters)-1) + txes = make([]*transaction.Transaction, 0, len(voters)-1) var refTxFee int64 - for i := range voters { + for i := range voters[:len(voters)-1] { h := voters[i].ScriptHash() refH := referenceAccounts[i].ScriptHash() gasBalance[i] = e.Chain.GetUtilityTokenBalance(h) @@ -169,7 +171,7 @@ func TestNEO_Vote(t *testing.T) { // GAS increase consists of 2 parts: NEO holding + voting for committee nodes. // Here we check that 2-nd part exists and is proportional to the amount of NEO given. - for i := range voters { + for i := range voters[:len(voters)-1] { newGAS := e.Chain.GetUtilityTokenBalance(voters[i].ScriptHash()) newGAS.Sub(newGAS, gasBalance[i]) gasForHold := referenceGASBalance[i] @@ -197,6 +199,7 @@ func TestNEO_Vote(t *testing.T) { require.NoError(t, err) for i := range pubs { require.NotEqual(t, candidates[0], pubs[i]) + require.NotEqual(t, candidates[len(candidates)-1], pubs[i]) } } diff --git a/pkg/crypto/keys/publickey.go b/pkg/crypto/keys/publickey.go index 56c3f4a48..b1e3748c2 100644 --- a/pkg/crypto/keys/publickey.go +++ b/pkg/crypto/keys/publickey.go @@ -12,7 +12,6 @@ import ( "github.com/btcsuite/btcd/btcec" lru "github.com/hashicorp/golang-lru" - "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/io" @@ -319,8 +318,7 @@ func (p *PublicKey) GetVerificationScript() []byte { buf.WriteB(0xAC) // CHECKSIG return buf.Bytes() } - emit.Bytes(buf.BinWriter, b) - emit.Syscall(buf.BinWriter, interopnames.SystemCryptoCheckSig) + emit.CheckSig(buf.BinWriter, b) return buf.Bytes() } diff --git a/pkg/vm/emit/emit.go b/pkg/vm/emit/emit.go index bff6807fd..1a8450f3f 100644 --- a/pkg/vm/emit/emit.go +++ b/pkg/vm/emit/emit.go @@ -219,6 +219,14 @@ func AppCall(w *io.BinWriter, scriptHash util.Uint160, operation string, f callf AppCallNoArgs(w, scriptHash, operation, f) } +// CheckSig emits a single-key verification script using given []bytes as a key. +// It does not check for key correctness, so you can get an invalid script if the +// data passed is not really a public key. +func CheckSig(w *io.BinWriter, key []byte) { + Bytes(w, key) + Syscall(w, interopnames.SystemCryptoCheckSig) +} + func isInstructionJmp(op opcode.Opcode) bool { return opcode.JMP <= op && op <= opcode.CALLL || op == opcode.ENDTRYL }