From 869c7d6afa042dcb2a8ca750ec27b5bb26f40f72 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Mon, 20 Apr 2020 20:38:47 +0300 Subject: [PATCH 01/13] core: init native interops in the genesis block closes #836 --- pkg/core/blockchain.go | 28 ++++++++++++++++--------- pkg/core/helper_test.go | 2 +- pkg/core/native/interop.go | 12 +++++++++++ pkg/core/native/native_gas.go | 4 ++-- pkg/core/native/native_neo.go | 4 ++-- pkg/core/util.go | 19 +++++++++++++++++ pkg/core/util_test.go | 2 +- pkg/rpc/server/server_test.go | 2 +- pkg/rpc/server/testdata/testblocks.acc | Bin 204257 -> 204257 bytes 9 files changed, 56 insertions(+), 17 deletions(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index aab57ddd6..f16d02232 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -197,9 +197,6 @@ func (bc *Blockchain) init() error { if err != nil { return err } - if err := bc.initNative(); err != nil { - return err - } return bc.storeBlock(genesisBlock) } if ver != version { @@ -211,6 +208,10 @@ func (bc *Blockchain) init() error { // and the genesis block as first block. bc.log.Info("restoring blockchain", zap.String("version", version)) + if err = bc.registerNative(); err != nil { + return err + } + bHeight, err := bc.dao.GetCurrentBlockHeight() if err != nil { return err @@ -272,19 +273,26 @@ func (bc *Blockchain) init() error { return nil } -func (bc *Blockchain) initNative() error { - ic := bc.newInteropContext(trigger.Application, bc.dao, nil, nil) - +func (bc *Blockchain) registerNative() error { gas := native.NewGAS() neo := native.NewNEO() neo.GAS = gas gas.NEO = neo - if err := gas.Initialize(ic); err != nil { - return fmt.Errorf("can't initialize GAS native contract: %v", err) + data, err := bc.dao.GetNativeContractState(gas.Hash) + if err != nil { + return err } - if err := neo.Initialize(ic); err != nil { - return fmt.Errorf("can't initialize NEO native contract: %v", err) + if err = gas.InitFromStore(data); err != nil { + return err + } + + data, err = bc.dao.GetNativeContractState(neo.Hash) + if err != nil { + return err + } + if err = neo.InitFromStore(data); err != nil { + return err } bc.contracts.SetGAS(gas) diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index a198fbf71..f54b1ad23 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -195,7 +195,7 @@ func TestCreateBasicChain(t *testing.T) { // use output of issue tx from genesis block as an input genesisBlock, err := bc.GetBlock(bc.GetHeaderHash(0)) require.NoError(t, err) - require.Equal(t, 4, len(genesisBlock.Transactions)) + require.Equal(t, 5, len(genesisBlock.Transactions)) h := genesisBlock.Transactions[3].Hash() txMoveNeo.AddInput(&transaction.Input{ PrevHash: h, diff --git a/pkg/core/native/interop.go b/pkg/core/native/interop.go index 6b6cec16e..351e226b1 100644 --- a/pkg/core/native/interop.go +++ b/pkg/core/native/interop.go @@ -2,6 +2,7 @@ package native import ( "errors" + "fmt" "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/vm" @@ -12,5 +13,16 @@ func Deploy(ic *interop.Context, _ *vm.VM) error { if ic.Block.Index != 0 { return errors.New("native contracts can be deployed only at 0 block") } + gas := NewGAS() + neo := NewNEO() + neo.GAS = gas + gas.NEO = neo + + if err := gas.Initialize(ic); err != nil { + return fmt.Errorf("can't initialize GAS native contract: %v", err) + } + if err := neo.Initialize(ic); err != nil { + return fmt.Errorf("can't initialize NEO native contract: %v", err) + } return nil } diff --git a/pkg/core/native/native_gas.go b/pkg/core/native/native_gas.go index 87736f0de..55e1084bf 100644 --- a/pkg/core/native/native_gas.go +++ b/pkg/core/native/native_gas.go @@ -45,7 +45,7 @@ func NewGAS() *GAS { } // initFromStore initializes variable contract parameters from the store. -func (g *GAS) initFromStore(data []byte) error { +func (g *GAS) InitFromStore(data []byte) error { g.totalSupply = *emit.BytesToInt(data) return nil } @@ -68,7 +68,7 @@ func (g *GAS) increaseBalance(_ *interop.Context, acc *state.Account, amount *bi func (g *GAS) Initialize(ic *interop.Context) error { data, err := ic.DAO.GetNativeContractState(g.Hash) if err == nil { - return g.initFromStore(data) + return g.InitFromStore(data) } else if err != storage.ErrKeyNotFound { return err } diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 71498a187..7624fcb23 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -76,7 +76,7 @@ func NewNEO() *NEO { func (n *NEO) Initialize(ic *interop.Context) error { data, err := ic.DAO.GetNativeContractState(n.Hash) if err == nil { - return n.initFromStore(data) + return n.InitFromStore(data) } else if err != storage.ErrKeyNotFound { return err } @@ -101,7 +101,7 @@ func (n *NEO) Initialize(ic *interop.Context) error { } // initFromStore initializes variable contract parameters from the store. -func (n *NEO) initFromStore(data []byte) error { +func (n *NEO) InitFromStore(data []byte) error { n.totalSupply = *emit.BytesToInt(data) return nil } diff --git a/pkg/core/util.go b/pkg/core/util.go index 2eeb9adea..465e90d11 100644 --- a/pkg/core/util.go +++ b/pkg/core/util.go @@ -8,8 +8,10 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/transaction" "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/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "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/opcode" ) @@ -86,6 +88,7 @@ func createGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error) &governingTokenTX, &utilityTokenTX, issueTx, + deployNativeContracts(), }, } @@ -96,6 +99,22 @@ func createGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error) return b, nil } +func deployNativeContracts() *transaction.Transaction { + buf := io.NewBufBinWriter() + emit.Syscall(buf.BinWriter, "Neo.Native.Deploy") + script := buf.Bytes() + tx := transaction.NewInvocationTX(script, 0) + tx.Nonce = 0 + tx.Sender = hash.Hash160([]byte{byte(opcode.PUSH1)}) + tx.Scripts = []transaction.Witness{ + { + InvocationScript: []byte{}, + VerificationScript: []byte{byte(opcode.PUSH1)}, + }, + } + return tx +} + func init() { admin := hash.Hash160([]byte{byte(opcode.OLDPUSH1)}) registerTX := &transaction.RegisterTX{ diff --git a/pkg/core/util_test.go b/pkg/core/util_test.go index ba870d324..a3b9fc001 100644 --- a/pkg/core/util_test.go +++ b/pkg/core/util_test.go @@ -20,7 +20,7 @@ func TestGenesisBlockMainNet(t *testing.T) { // have been changed. Consequently, hash of the genesis block has been changed. // Update expected genesis block hash for better times. // Old hash is "d42561e3d30e15be6400b6df2f328e02d2bf6354c41dce433bc57687c82144bf" - expect := "931161239581bcd90b73d092483dee6b7bf2162e66df7e15c3a276a22b4c9422" + expect := "094c2c2db5dcb868d85aa4d652aed23bc67e7166f53223a228e382265b1be84b" assert.Equal(t, expect, block.Hash().StringLE()) } diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 49a494a65..ab5e059de 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -408,7 +408,7 @@ var rpcTestCases = map[string][]rpcTestCase{ "getblockheader": { { name: "invalid verbose type", - params: `["614a9085dc55fd0539ad3a9d68d8b8e7c52328da905c87bfe8cfca57a5c3c02f", true]`, + params: `["9673799c5b5a294427401cb07d6cc615ada3a0d5c5bf7ed6f0f54f24abb2e2ac", true]`, fail: true, }, { diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index ab268fe5dbc8ec776157e31d6728bd03e6adc961..ba7136e2697c18ed16dcb414fe147ef9251ec864 100644 GIT binary patch delta 66931 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

Lrb@Q)LEyxQqF) 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 delta 66929 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 QHp!0vlN_vxVLhS$13BbZp8x;= From 30836ca69bf58935100e27349a29a9453c543492 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Wed, 22 Apr 2020 23:00:18 +0300 Subject: [PATCH 02/13] core/native: untangle native contracts initialization The notion of NativeContractState shouldn't ever existed, native contract is a contract and its state is saved as regular contract state which is critical because we'll have MPT calculations over this state soon. Initial minting should be done in Neo.Native.Deploy because it generates notification that should have proper transaction context. RegisterNative() shouldn't exist as a public method, native contracts are only registered at block 0 and they can do it internally, no outside user should be able to mess with it. Move some structures from `native` package to `interop` also to avoid circular references as interop.Context has to have a list of native contracts (exposing them via Blockchainer is again too dangerous, it's too powerful tool). --- pkg/core/blockchain.go | 39 +----------- pkg/core/dao/dao.go | 14 ---- pkg/core/interop/context.go | 71 ++++++++++++++++++++- pkg/core/native/contract.go | 106 +++++-------------------------- pkg/core/native/interop.go | 14 ++-- pkg/core/native/native_gas.go | 34 ++++------ pkg/core/native/native_neo.go | 27 +++----- pkg/core/native/native_nep5.go | 59 ++++++++++++----- pkg/core/native_contract_test.go | 22 +++++-- pkg/core/storage/store.go | 1 - 10 files changed, 171 insertions(+), 216 deletions(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index f16d02232..c3a1034c0 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -208,10 +208,6 @@ func (bc *Blockchain) init() error { // and the genesis block as first block. bc.log.Info("restoring blockchain", zap.String("version", version)) - if err = bc.registerNative(); err != nil { - return err - } - bHeight, err := bc.dao.GetCurrentBlockHeight() if err != nil { return err @@ -273,34 +269,6 @@ func (bc *Blockchain) init() error { return nil } -func (bc *Blockchain) registerNative() error { - gas := native.NewGAS() - neo := native.NewNEO() - neo.GAS = gas - gas.NEO = neo - - data, err := bc.dao.GetNativeContractState(gas.Hash) - if err != nil { - return err - } - if err = gas.InitFromStore(data); err != nil { - return err - } - - data, err = bc.dao.GetNativeContractState(neo.Hash) - if err != nil { - return err - } - if err = neo.InitFromStore(data); err != nil { - return err - } - - bc.contracts.SetGAS(gas) - bc.contracts.SetNEO(neo) - - return nil -} - // Run runs chain loop. func (bc *Blockchain) Run() { persistTimer := time.NewTimer(persistInterval) @@ -855,11 +823,6 @@ func (bc *Blockchain) LastBatch() *storage.MemBatch { return bc.lastBatch } -// RegisterNative registers native contract in the blockchain. -func (bc *Blockchain) RegisterNative(c native.Contract) { - bc.contracts.Add(c) -} - // processOutputs processes transaction outputs. func processOutputs(tx *transaction.Transaction, dao *dao.Cached) error { for index, output := range tx.Outputs { @@ -2065,7 +2028,7 @@ func (bc *Blockchain) secondsPerBlock() int { } func (bc *Blockchain) newInteropContext(trigger trigger.Type, d dao.DAO, block *block.Block, tx *transaction.Transaction) *interop.Context { - ic := interop.NewContext(trigger, bc, d, block, tx, bc.log) + ic := interop.NewContext(trigger, bc, d, bc.contracts.Contracts, block, tx, bc.log) switch { case tx != nil: ic.Container = tx diff --git a/pkg/core/dao/dao.go b/pkg/core/dao/dao.go index c5a13a338..34b729636 100644 --- a/pkg/core/dao/dao.go +++ b/pkg/core/dao/dao.go @@ -32,7 +32,6 @@ type DAO interface { GetCurrentBlockHeight() (uint32, error) GetCurrentHeaderHeight() (i uint32, h util.Uint256, err error) GetHeaderHashes() ([]util.Uint256, error) - GetNativeContractState(h util.Uint160) ([]byte, error) GetNEP5Balances(acc util.Uint160) (*state.NEP5Balances, error) GetNEP5TransferLog(acc util.Uint160, index uint32) (*state.NEP5TransferLog, error) GetNextBlockValidators() (keys.PublicKeys, error) @@ -55,7 +54,6 @@ type DAO interface { PutAssetState(as *state.Asset) error PutContractState(cs *state.Contract) error PutCurrentHeader(hashAndIndex []byte) error - PutNativeContractState(h util.Uint160, value []byte) error PutNEP5Balances(acc util.Uint160, bs *state.NEP5Balances) error PutNEP5TransferLog(acc util.Uint160, index uint32, lg *state.NEP5TransferLog) error PutNextBlockValidators(keys.PublicKeys) error @@ -211,18 +209,6 @@ func (dao *Simple) DeleteContractState(hash util.Uint160) error { return dao.Store.Delete(key) } -// GetNativeContractState retrieves native contract state from the store. -func (dao *Simple) GetNativeContractState(h util.Uint160) ([]byte, error) { - key := storage.AppendPrefix(storage.STNativeContract, h.BytesBE()) - return dao.Store.Get(key) -} - -// PutNativeContractState puts native contract state into the store. -func (dao *Simple) PutNativeContractState(h util.Uint160, value []byte) error { - key := storage.AppendPrefix(storage.STNativeContract, h.BytesBE()) - return dao.Store.Put(key, value) -} - // -- end contracts. // -- start nep5 balances. diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index d384fb3d1..ff42e8fa4 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -7,8 +7,14 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto" + "github.com/nspcc-dev/neo-go/pkg/crypto/hash" + "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" + "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/emit" "go.uber.org/zap" ) @@ -16,6 +22,7 @@ import ( type Context struct { Chain blockchainer.Blockchainer Container crypto.Verifiable + Natives []Contract Trigger trigger.Type Block *block.Block Tx *transaction.Transaction @@ -25,11 +32,12 @@ type Context struct { } // NewContext returns new interop context. -func NewContext(trigger trigger.Type, bc blockchainer.Blockchainer, d dao.DAO, block *block.Block, tx *transaction.Transaction, log *zap.Logger) *Context { +func NewContext(trigger trigger.Type, bc blockchainer.Blockchainer, d dao.DAO, natives []Contract, block *block.Block, tx *transaction.Transaction, log *zap.Logger) *Context { dao := dao.NewCached(d) nes := make([]state.NotificationEvent, 0) return &Context{ Chain: bc, + Natives: natives, Trigger: trigger, Block: block, Tx: tx, @@ -48,3 +56,64 @@ type Function struct { Func func(*Context, *vm.VM) error Price int } + +// Method is a signature for a native method. +type Method = func(ic *Context, args []vm.StackItem) vm.StackItem + +// MethodAndPrice is a native-contract method descriptor. +type MethodAndPrice struct { + Func Method + Price int64 + RequiredFlags smartcontract.CallFlag +} + +// Contract is an interface for all native contracts. +type Contract interface { + Initialize(*Context) error + Metadata() *ContractMD + OnPersist(*Context) error +} + +// ContractMD represents native contract instance. +type ContractMD struct { + Manifest manifest.Manifest + ServiceName string + ServiceID uint32 + Script []byte + Hash util.Uint160 + Methods map[string]MethodAndPrice +} + +// NewContractMD returns Contract with the specified list of methods. +func NewContractMD(name string) *ContractMD { + c := &ContractMD{ + ServiceName: name, + ServiceID: emit.InteropNameToID([]byte(name)), + Methods: make(map[string]MethodAndPrice), + } + + w := io.NewBufBinWriter() + emit.Syscall(w.BinWriter, c.ServiceName) + c.Script = w.Bytes() + c.Hash = hash.Hash160(c.Script) + c.Manifest = *manifest.DefaultManifest(c.Hash) + + return c +} + +// AddMethod adds new method to a native contract. +func (c *ContractMD) AddMethod(md *MethodAndPrice, desc *manifest.Method, safe bool) { + c.Manifest.ABI.Methods = append(c.Manifest.ABI.Methods, *desc) + c.Methods[desc.Name] = *md + if safe { + c.Manifest.SafeMethods.Add(desc.Name) + } +} + +// AddEvent adds new event to a native contract. +func (c *ContractMD) AddEvent(name string, ps ...manifest.Parameter) { + c.Manifest.ABI.Events = append(c.Manifest.ABI.Events, manifest.Event{ + Name: name, + Parameters: ps, + }) +} diff --git a/pkg/core/native/contract.go b/pkg/core/native/contract.go index 332276320..a4b83e751 100644 --- a/pkg/core/native/contract.go +++ b/pkg/core/native/contract.go @@ -4,80 +4,20 @@ import ( "fmt" "github.com/nspcc-dev/neo-go/pkg/core/interop" - "github.com/nspcc-dev/neo-go/pkg/crypto/hash" - "github.com/nspcc-dev/neo-go/pkg/io" - "github.com/nspcc-dev/neo-go/pkg/smartcontract" - "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" - "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/pkg/errors" ) -// Method is a signature for a native method. -type Method = func(ic *interop.Context, args []vm.StackItem) vm.StackItem - -// MethodAndPrice is a native-contract method descriptor. -type MethodAndPrice struct { - Func Method - Price int64 - RequiredFlags smartcontract.CallFlag -} - -// Contract is an interface for all native contracts. -type Contract interface { - Metadata() *ContractMD - OnPersist(*interop.Context) error -} - -// ContractMD represents native contract instance. -type ContractMD struct { - Manifest manifest.Manifest - ServiceName string - ServiceID uint32 - Script []byte - Hash util.Uint160 - Methods map[string]MethodAndPrice -} - // Contracts is a set of registered native contracts. type Contracts struct { NEO *NEO GAS *GAS - Contracts []Contract -} - -// SetGAS sets GAS native contract. -func (cs *Contracts) SetGAS(g *GAS) { - cs.GAS = g - cs.Contracts = append(cs.Contracts, g) -} - -// SetNEO sets NEO native contract. -func (cs *Contracts) SetNEO(n *NEO) { - cs.NEO = n - cs.Contracts = append(cs.Contracts, n) -} - -// NewContractMD returns Contract with the specified list of methods. -func NewContractMD(name string) *ContractMD { - c := &ContractMD{ - ServiceName: name, - ServiceID: emit.InteropNameToID([]byte(name)), - Methods: make(map[string]MethodAndPrice), - } - - w := io.NewBufBinWriter() - emit.Syscall(w.BinWriter, c.ServiceName) - c.Script = w.Bytes() - c.Hash = hash.Hash160(c.Script) - c.Manifest = *manifest.DefaultManifest(c.Hash) - - return c + Contracts []interop.Contract } // ByHash returns native contract with the specified hash. -func (cs *Contracts) ByHash(h util.Uint160) Contract { +func (cs *Contracts) ByHash(h util.Uint160) interop.Contract { for _, ctr := range cs.Contracts { if ctr.Metadata().Hash.Equals(h) { return ctr @@ -87,7 +27,7 @@ func (cs *Contracts) ByHash(h util.Uint160) Contract { } // ByID returns native contract with the specified id. -func (cs *Contracts) ByID(id uint32) Contract { +func (cs *Contracts) ByID(id uint32) interop.Contract { for _, ctr := range cs.Contracts { if ctr.Metadata().ServiceID == id { return ctr @@ -96,33 +36,21 @@ func (cs *Contracts) ByID(id uint32) Contract { return nil } -// AddMethod adds new method to a native contract. -func (c *ContractMD) AddMethod(md *MethodAndPrice, desc *manifest.Method, safe bool) { - c.Manifest.ABI.Methods = append(c.Manifest.ABI.Methods, *desc) - c.Methods[desc.Name] = *md - if safe { - c.Manifest.SafeMethods.Add(desc.Name) - } -} - -// AddEvent adds new event to a native contract. -func (c *ContractMD) AddEvent(name string, ps ...manifest.Parameter) { - c.Manifest.ABI.Events = append(c.Manifest.ABI.Events, manifest.Event{ - Name: name, - Parameters: ps, - }) -} - -// NewContracts returns new empty set of native contracts. +// NewContracts returns new set of native contracts with new GAS and NEO +// contracts. func NewContracts() *Contracts { - return &Contracts{ - Contracts: []Contract{}, - } -} + cs := new(Contracts) -// Add adds new native contracts to the list. -func (cs *Contracts) Add(c Contract) { - cs.Contracts = append(cs.Contracts, c) + gas := NewGAS() + neo := NewNEO() + neo.GAS = gas + gas.NEO = neo + + cs.GAS = gas + cs.Contracts = append(cs.Contracts, gas) + cs.NEO = neo + cs.Contracts = append(cs.Contracts, neo) + return cs } // GetNativeInterop returns an interop getter for a given set of contracts. @@ -139,7 +67,7 @@ func (cs *Contracts) GetNativeInterop(ic *interop.Context) func(uint32) *vm.Inte } // getNativeInterop returns native contract interop. -func getNativeInterop(ic *interop.Context, c Contract) func(v *vm.VM) error { +func getNativeInterop(ic *interop.Context, c interop.Contract) func(v *vm.VM) error { return func(v *vm.VM) error { h := v.GetContextScriptHash(0) if !h.Equals(c.Metadata().Hash) { diff --git a/pkg/core/native/interop.go b/pkg/core/native/interop.go index 351e226b1..b535313f9 100644 --- a/pkg/core/native/interop.go +++ b/pkg/core/native/interop.go @@ -13,16 +13,12 @@ func Deploy(ic *interop.Context, _ *vm.VM) error { if ic.Block.Index != 0 { return errors.New("native contracts can be deployed only at 0 block") } - gas := NewGAS() - neo := NewNEO() - neo.GAS = gas - gas.NEO = neo - if err := gas.Initialize(ic); err != nil { - return fmt.Errorf("can't initialize GAS native contract: %v", err) - } - if err := neo.Initialize(ic); err != nil { - return fmt.Errorf("can't initialize NEO native contract: %v", err) + for _, native := range ic.Natives { + if err := native.Initialize(ic); err != nil { + md := native.Metadata() + return fmt.Errorf("initializing %s native contract: %v", md.ServiceName, err) + } } return nil } diff --git a/pkg/core/native/native_gas.go b/pkg/core/native/native_gas.go index 55e1084bf..b95ab5464 100644 --- a/pkg/core/native/native_gas.go +++ b/pkg/core/native/native_gas.go @@ -6,14 +6,12 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/state" - "github.com/nspcc-dev/neo-go/pkg/core/storage" "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/smartcontract" "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" - "github.com/nspcc-dev/neo-go/pkg/vm/emit" ) // GAS represents GAS native contract. @@ -24,13 +22,17 @@ type GAS struct { const gasSyscallName = "Neo.Native.Tokens.GAS" +// GASFactor is a divisor for finding GAS integral value. +const GASFactor = NEOTotalSupply +const initialGAS = 30000000 + // NewGAS returns GAS native contract. func NewGAS() *GAS { nep5 := newNEP5Native(gasSyscallName) nep5.name = "GAS" nep5.symbol = "gas" nep5.decimals = 8 - nep5.factor = 100000000 + nep5.factor = GASFactor g := &GAS{nep5TokenNative: *nep5} @@ -44,16 +46,6 @@ func NewGAS() *GAS { return g } -// initFromStore initializes variable contract parameters from the store. -func (g *GAS) InitFromStore(data []byte) error { - g.totalSupply = *emit.BytesToInt(data) - return nil -} - -func (g *GAS) serializeState() []byte { - return emit.IntToBytes(&g.totalSupply) -} - func (g *GAS) increaseBalance(_ *interop.Context, acc *state.Account, amount *big.Int) error { if sign := amount.Sign(); sign == 0 { return nil @@ -66,22 +58,18 @@ func (g *GAS) increaseBalance(_ *interop.Context, acc *state.Account, amount *bi // Initialize initializes GAS contract. func (g *GAS) Initialize(ic *interop.Context) error { - data, err := ic.DAO.GetNativeContractState(g.Hash) - if err == nil { - return g.InitFromStore(data) - } else if err != storage.ErrKeyNotFound { + if err := g.nep5TokenNative.Initialize(ic); err != nil { return err } - - if err := g.nep5TokenNative.Initialize(); err != nil { - return err + if g.nep5TokenNative.getTotalSupply(ic).Sign() != 0 { + return errors.New("already initialized") } h, _, err := getStandbyValidatorsHash(ic) if err != nil { return err } - g.mint(ic, h, big.NewInt(30000000*g.factor)) - return ic.DAO.PutNativeContractState(g.Hash, g.serializeState()) + g.mint(ic, h, big.NewInt(initialGAS*GASFactor)) + return nil } // OnPersist implements Contract interface. @@ -95,7 +83,7 @@ func (g *GAS) OnPersist(ic *interop.Context) error { // netFee += tx.NetworkFee //} //g.mint(ic, , netFee) - return ic.DAO.PutNativeContractState(g.Hash, g.serializeState()) + return nil } func (g *GAS) getSysFeeAmount(ic *interop.Context, args []vm.StackItem) vm.StackItem { diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 7624fcb23..8ebb5a526 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -15,7 +15,6 @@ import ( "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" - "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/pkg/errors" ) @@ -27,6 +26,9 @@ type NEO struct { const neoSyscallName = "Neo.Native.Tokens.NEO" +// NEOTotalSupply is the total amount of NEO in the system. +const NEOTotalSupply = 100000000 + // NewNEO returns NEO native contract. func NewNEO() *NEO { nep5 := newNEP5Native(neoSyscallName) @@ -74,22 +76,19 @@ func NewNEO() *NEO { // Initialize initializes NEO contract. func (n *NEO) Initialize(ic *interop.Context) error { - data, err := ic.DAO.GetNativeContractState(n.Hash) - if err == nil { - return n.InitFromStore(data) - } else if err != storage.ErrKeyNotFound { + if err := n.nep5TokenNative.Initialize(ic); err != nil { return err } - if err := n.nep5TokenNative.Initialize(); err != nil { - return err + if n.nep5TokenNative.getTotalSupply(ic).Sign() != 0 { + return errors.New("already initialized") } h, vs, err := getStandbyValidatorsHash(ic) if err != nil { return err } - n.mint(ic, h, big.NewInt(100000000*n.factor)) + n.mint(ic, h, big.NewInt(NEOTotalSupply)) for i := range vs { if err := n.registerValidatorInternal(ic, vs[i]); err != nil { @@ -97,19 +96,9 @@ func (n *NEO) Initialize(ic *interop.Context) error { } } - return ic.DAO.PutNativeContractState(n.Hash, n.serializeState()) -} - -// initFromStore initializes variable contract parameters from the store. -func (n *NEO) InitFromStore(data []byte) error { - n.totalSupply = *emit.BytesToInt(data) return nil } -func (n *NEO) serializeState() []byte { - return emit.IntToBytes(&n.totalSupply) -} - // OnPersist implements Contract interface. func (n *NEO) OnPersist(ic *interop.Context) error { pubs, err := n.GetValidatorsInternal(ic.Chain, ic.DAO) @@ -119,7 +108,7 @@ func (n *NEO) OnPersist(ic *interop.Context) error { if err := ic.DAO.PutNextBlockValidators(pubs); err != nil { return err } - return ic.DAO.PutNativeContractState(n.Hash, n.serializeState()) + return nil } func (n *NEO) increaseBalance(ic *interop.Context, acc *state.Account, amount *big.Int) error { diff --git a/pkg/core/native/native_nep5.go b/pkg/core/native/native_nep5.go index e4476be14..676437369 100644 --- a/pkg/core/native/native_nep5.go +++ b/pkg/core/native/native_nep5.go @@ -10,28 +10,31 @@ import ( "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" + "github.com/nspcc-dev/neo-go/pkg/vm/emit" ) // nep5TokenNative represents NEP-5 token contract. type nep5TokenNative struct { - ContractMD - name string - symbol string - decimals int64 - factor int64 - totalSupply big.Int - onPersist func(*interop.Context) error - incBalance func(*interop.Context, *state.Account, *big.Int) error + interop.ContractMD + name string + symbol string + decimals int64 + factor int64 + onPersist func(*interop.Context) error + incBalance func(*interop.Context, *state.Account, *big.Int) error } -func (c *nep5TokenNative) Metadata() *ContractMD { +// totalSupplyKey is the key used to store totalSupply value. +var totalSupplyKey = []byte{11} + +func (c *nep5TokenNative) Metadata() *interop.ContractMD { return &c.ContractMD } -var _ Contract = (*nep5TokenNative)(nil) +var _ interop.Contract = (*nep5TokenNative)(nil) func newNEP5Native(name string) *nep5TokenNative { - n := &nep5TokenNative{ContractMD: *NewContractMD(name)} + n := &nep5TokenNative{ContractMD: *interop.NewContractMD(name)} desc := newDescriptor("name", smartcontract.StringType) md := newMethodAndPrice(n.Name, 1, smartcontract.NoneFlag) @@ -45,6 +48,10 @@ func newNEP5Native(name string) *nep5TokenNative { md = newMethodAndPrice(n.Decimals, 1, smartcontract.NoneFlag) n.AddMethod(md, desc, true) + desc = newDescriptor("totalSupply", smartcontract.IntegerType) + md = newMethodAndPrice(n.TotalSupply, 1, smartcontract.NoneFlag) + n.AddMethod(md, desc, true) + desc = newDescriptor("balanceOf", smartcontract.IntegerType, manifest.NewParameter("account", smartcontract.Hash160Type)) md = newMethodAndPrice(n.balanceOf, 1, smartcontract.NoneFlag) @@ -63,7 +70,7 @@ func newNEP5Native(name string) *nep5TokenNative { return n } -func (c *nep5TokenNative) Initialize() error { +func (c *nep5TokenNative) Initialize(_ *interop.Context) error { return nil } @@ -79,6 +86,23 @@ func (c *nep5TokenNative) Decimals(_ *interop.Context, _ []vm.StackItem) vm.Stac return vm.NewBigIntegerItem(big.NewInt(c.decimals)) } +func (c *nep5TokenNative) TotalSupply(ic *interop.Context, _ []vm.StackItem) vm.StackItem { + return vm.NewBigIntegerItem(c.getTotalSupply(ic)) +} + +func (c *nep5TokenNative) getTotalSupply(ic *interop.Context) *big.Int { + si := ic.DAO.GetStorageItem(c.Hash, totalSupplyKey) + if si == nil { + return big.NewInt(0) + } + return emit.BytesToInt(si.Value) +} + +func (c *nep5TokenNative) saveTotalSupply(ic *interop.Context, supply *big.Int) error { + si := &state.StorageItem{Value: emit.IntToBytes(supply)} + return ic.DAO.PutStorageItem(c.Hash, totalSupplyKey, si) +} + func (c *nep5TokenNative) Transfer(ic *interop.Context, args []vm.StackItem) vm.StackItem { from := toUint160(args[0]) to := toUint160(args[1]) @@ -185,7 +209,12 @@ func (c *nep5TokenNative) addTokens(ic *interop.Context, h util.Uint160, amount panic(err) } - c.totalSupply.Add(&c.totalSupply, amount) + supply := c.getTotalSupply(ic) + supply.Add(supply, amount) + err = c.saveTotalSupply(ic, supply) + if err != nil { + panic(err) + } } func (c *nep5TokenNative) OnPersist(ic *interop.Context) error { @@ -200,8 +229,8 @@ func newDescriptor(name string, ret smartcontract.ParamType, ps ...manifest.Para } } -func newMethodAndPrice(f Method, price int64, flags smartcontract.CallFlag) *MethodAndPrice { - return &MethodAndPrice{ +func newMethodAndPrice(f interop.Method, price int64, flags smartcontract.CallFlag) *interop.MethodAndPrice { + return &interop.MethodAndPrice{ Func: f, Price: price, RequiredFlags: flags, diff --git a/pkg/core/native_contract_test.go b/pkg/core/native_contract_test.go index 38a8a75f4..4e17a36b6 100644 --- a/pkg/core/native_contract_test.go +++ b/pkg/core/native_contract_test.go @@ -6,7 +6,6 @@ import ( "testing" "github.com/nspcc-dev/neo-go/pkg/core/interop" - "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract" @@ -17,11 +16,15 @@ import ( ) type testNative struct { - meta native.ContractMD + meta interop.ContractMD blocks chan uint32 } -func (tn *testNative) Metadata() *native.ContractMD { +func (tn *testNative) Initialize(_ *interop.Context) error { + return nil +} + +func (tn *testNative) Metadata() *interop.ContractMD { return &tn.meta } @@ -34,11 +37,16 @@ func (tn *testNative) OnPersist(ic *interop.Context) error { } } -var _ native.Contract = (*testNative)(nil) +var _ interop.Contract = (*testNative)(nil) + +// registerNative registers native contract in the blockchain. +func (bc *Blockchain) registerNative(c interop.Contract) { + bc.contracts.Contracts = append(bc.contracts.Contracts, c) +} func newTestNative() *testNative { tn := &testNative{ - meta: *native.NewContractMD("Test.Native.Sum"), + meta: *interop.NewContractMD("Test.Native.Sum"), blocks: make(chan uint32, 1), } desc := &manifest.Method{ @@ -49,7 +57,7 @@ func newTestNative() *testNative { }, ReturnType: smartcontract.IntegerType, } - md := &native.MethodAndPrice{ + md := &interop.MethodAndPrice{ Func: tn.sum, Price: 1, RequiredFlags: smartcontract.NoneFlag, @@ -76,7 +84,7 @@ func TestNativeContract_Invoke(t *testing.T) { defer chain.Close() tn := newTestNative() - chain.RegisterNative(tn) + chain.registerNative(tn) w := io.NewBufBinWriter() emit.AppCallWithOperationAndArgs(w.BinWriter, tn.Metadata().Hash, "sum", int64(14), int64(28)) diff --git a/pkg/core/storage/store.go b/pkg/core/storage/store.go index b9fb6874d..ea48dc0b8 100644 --- a/pkg/core/storage/store.go +++ b/pkg/core/storage/store.go @@ -17,7 +17,6 @@ const ( STAsset KeyPrefix = 0x4c STNotification KeyPrefix = 0x4d STContract KeyPrefix = 0x50 - STNativeContract KeyPrefix = 0x51 STStorage KeyPrefix = 0x70 STNEP5Transfers KeyPrefix = 0x72 STNEP5Balances KeyPrefix = 0x73 From 1dcace159422d5670807847ba08c0c88995ad8b7 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 23 Apr 2020 16:25:30 +0300 Subject: [PATCH 03/13] native: don't distribute gas at block 0 It fails at the moment and it doesn't make sense at conceptual level. --- pkg/core/native/native_neo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 8ebb5a526..127afe44c 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -125,7 +125,7 @@ func (n *NEO) increaseBalance(ic *interop.Context, acc *state.Account, amount *b } func (n *NEO) distributeGas(ic *interop.Context, acc *state.Account) error { - if ic.Block == nil { + if ic.Block == nil || ic.Block.Index == 0 { return nil } sys, net, err := ic.Chain.CalculateClaimable(util.Fixed8(acc.NEO.Balance.Int64()), acc.NEO.BalanceHeight, ic.Block.Index) From 8c02c6b22cac5d5b95a354fa249b9cf70f109ecc Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 23 Apr 2020 16:26:37 +0300 Subject: [PATCH 04/13] native: put proper Null StackItem for transfer event nil is not a good StackItem, we have proper VM-level Null for this. --- pkg/core/native/native_nep5.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/core/native/native_nep5.go b/pkg/core/native/native_nep5.go index 676437369..bb4cf8e87 100644 --- a/pkg/core/native/native_nep5.go +++ b/pkg/core/native/native_nep5.go @@ -113,7 +113,7 @@ func (c *nep5TokenNative) Transfer(ic *interop.Context, args []vm.StackItem) vm. func addrToStackItem(u *util.Uint160) vm.StackItem { if u == nil { - return nil + return vm.NullItem{} } return vm.NewByteArrayItem(u.BytesBE()) } From 4e8ee697ee317727b65301c931a00c2261224cd9 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 23 Apr 2020 21:28:37 +0300 Subject: [PATCH 05/13] native: store NEO and GAS state in the storage As it should be done (although current serialization format is not quite right). --- pkg/core/native/native_gas.go | 11 ++++-- pkg/core/native/native_neo.go | 69 +++++++++++++++++++++------------- pkg/core/native/native_nep5.go | 52 +++++++++++++++---------- pkg/core/state/account.go | 6 --- pkg/core/state/native_state.go | 49 ++++++++++++++++++++++++ 5 files changed, 133 insertions(+), 54 deletions(-) diff --git a/pkg/core/native/native_gas.go b/pkg/core/native/native_gas.go index b95ab5464..cdc0cd97b 100644 --- a/pkg/core/native/native_gas.go +++ b/pkg/core/native/native_gas.go @@ -46,13 +46,18 @@ func NewGAS() *GAS { return g } -func (g *GAS) increaseBalance(_ *interop.Context, acc *state.Account, amount *big.Int) error { +func (g *GAS) increaseBalance(_ *interop.Context, _ util.Uint160, si *state.StorageItem, amount *big.Int) error { + acc, err := state.NEP5BalanceStateFromBytes(si.Value) + if err != nil { + return err + } if sign := amount.Sign(); sign == 0 { return nil - } else if sign == -1 && acc.GAS.Balance.Cmp(new(big.Int).Neg(amount)) == -1 { + } else if sign == -1 && acc.Balance.Cmp(new(big.Int).Neg(amount)) == -1 { return errors.New("insufficient funds") } - acc.GAS.Balance.Add(&acc.GAS.Balance, amount) + acc.Balance.Add(&acc.Balance, amount) + si.Value = acc.Bytes() return nil } diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 127afe44c..7eda3bd22 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -111,29 +111,34 @@ func (n *NEO) OnPersist(ic *interop.Context) error { return nil } -func (n *NEO) increaseBalance(ic *interop.Context, acc *state.Account, amount *big.Int) error { - if sign := amount.Sign(); sign == 0 { - return nil - } else if sign == -1 && acc.NEO.Balance.Cmp(new(big.Int).Neg(amount)) == -1 { - return errors.New("insufficient funds") - } - if err := n.distributeGas(ic, acc); err != nil { - return err - } - acc.NEO.Balance.Add(&acc.NEO.Balance, amount) - return nil -} - -func (n *NEO) distributeGas(ic *interop.Context, acc *state.Account) error { - if ic.Block == nil || ic.Block.Index == 0 { - return nil - } - sys, net, err := ic.Chain.CalculateClaimable(util.Fixed8(acc.NEO.Balance.Int64()), acc.NEO.BalanceHeight, ic.Block.Index) +func (n *NEO) increaseBalance(ic *interop.Context, h util.Uint160, si *state.StorageItem, amount *big.Int) error { + acc, err := state.NEOBalanceStateFromBytes(si.Value) if err != nil { return err } - acc.NEO.BalanceHeight = ic.Block.Index - n.GAS.mint(ic, acc.ScriptHash, big.NewInt(int64(sys+net))) + if sign := amount.Sign(); sign == 0 { + return nil + } else if sign == -1 && acc.Balance.Cmp(new(big.Int).Neg(amount)) == -1 { + return errors.New("insufficient funds") + } + if err := n.distributeGas(ic, h, acc); err != nil { + return err + } + acc.Balance.Add(&acc.Balance, amount) + si.Value = acc.Bytes() + return nil +} + +func (n *NEO) distributeGas(ic *interop.Context, h util.Uint160, acc *state.NEOBalanceState) error { + if ic.Block == nil || ic.Block.Index == 0 { + return nil + } + sys, net, err := ic.Chain.CalculateClaimable(util.Fixed8(acc.Balance.Int64()), acc.BalanceHeight, ic.Block.Index) + if err != nil { + return err + } + acc.BalanceHeight = ic.Block.Index + n.GAS.mint(ic, h, big.NewInt(int64(sys+net))) return nil } @@ -192,12 +197,21 @@ func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pubs keys.Public } else if !ok { return errors.New("invalid signature") } - acc, err := ic.DAO.GetAccountState(h) + key := makeAccountKey(h) + si := ic.DAO.GetStorageItem(n.Hash, key) + if si == nil { + return errors.New("invalid account") + } + acc, err := state.NEOBalanceStateFromBytes(si.Value) if err != nil { return err } - balance := util.Fixed8(acc.NEO.Balance.Int64()) - if err := ModifyAccountVotes(acc, ic.DAO, -balance); err != nil { + oldAcc, err := ic.DAO.GetAccountState(h) + if err != nil { + return err + } + balance := util.Fixed8(acc.Balance.Int64()) + if err := ModifyAccountVotes(oldAcc, ic.DAO, -balance); err != nil { return err } pubs = pubs.Unique() @@ -212,7 +226,7 @@ func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pubs keys.Public } newPubs = append(newPubs, pub) } - if lp, lv := len(newPubs), len(acc.Votes); lp != lv { + if lp, lv := len(newPubs), len(oldAcc.Votes); lp != lv { vc, err := ic.DAO.GetValidatorsCount() if err != nil { return err @@ -227,8 +241,11 @@ func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pubs keys.Public return err } } - acc.Votes = newPubs - return ModifyAccountVotes(acc, ic.DAO, balance) + oldAcc.Votes = newPubs + if err := ModifyAccountVotes(oldAcc, ic.DAO, balance); err != nil { + return err + } + return ic.DAO.PutAccountState(oldAcc) } // ModifyAccountVotes modifies votes of the specified account by value (can be negative). diff --git a/pkg/core/native/native_nep5.go b/pkg/core/native/native_nep5.go index bb4cf8e87..021eb6b9c 100644 --- a/pkg/core/native/native_nep5.go +++ b/pkg/core/native/native_nep5.go @@ -13,6 +13,17 @@ import ( "github.com/nspcc-dev/neo-go/pkg/vm/emit" ) +// prefixAccount is the standard prefix used to store account data. +const prefixAccount = 20 + +// makeAccountKey creates a key from account script hash. +func makeAccountKey(h util.Uint160) []byte { + k := make([]byte, util.Uint160Size+1) + k[0] = prefixAccount + copy(k[1:], h.BytesBE()) + return k +} + // nep5TokenNative represents NEP-5 token contract. type nep5TokenNative struct { interop.ContractMD @@ -21,7 +32,7 @@ type nep5TokenNative struct { decimals int64 factor int64 onPersist func(*interop.Context) error - incBalance func(*interop.Context, *state.Account, *big.Int) error + incBalance func(*interop.Context, util.Uint160, *state.StorageItem, *big.Int) error } // totalSupplyKey is the key used to store totalSupply value. @@ -136,9 +147,10 @@ func (c *nep5TokenNative) transfer(ic *interop.Context, from, to util.Uint160, a return errors.New("negative amount") } - accFrom, err := ic.DAO.GetAccountStateOrNew(from) - if err != nil { - return err + keyFrom := makeAccountKey(from) + siFrom := ic.DAO.GetStorageItem(c.Hash, keyFrom) + if siFrom == nil { + return errors.New("insufficient funds") } isEmpty := from.Equals(to) || amount.Sign() == 0 @@ -146,22 +158,23 @@ func (c *nep5TokenNative) transfer(ic *interop.Context, from, to util.Uint160, a if isEmpty { inc = big.NewInt(0) } - if err := c.incBalance(ic, accFrom, inc); err != nil { + if err := c.incBalance(ic, from, siFrom, inc); err != nil { return err } - if err := ic.DAO.PutAccountState(accFrom); err != nil { + if err := ic.DAO.PutStorageItem(c.Hash, keyFrom, siFrom); err != nil { return err } if !isEmpty { - accTo, err := ic.DAO.GetAccountStateOrNew(to) - if err != nil { + keyTo := makeAccountKey(to) + siTo := ic.DAO.GetStorageItem(c.Hash, keyTo) + if siTo == nil { + siTo = new(state.StorageItem) + } + if err := c.incBalance(ic, to, siTo, amount); err != nil { return err } - if err := c.incBalance(ic, accTo, amount); err != nil { - return err - } - if err := ic.DAO.PutAccountState(accTo); err != nil { + if err := ic.DAO.PutStorageItem(c.Hash, keyTo, siTo); err != nil { return err } } @@ -198,20 +211,21 @@ func (c *nep5TokenNative) addTokens(ic *interop.Context, h util.Uint160, amount return } - acc, err := ic.DAO.GetAccountStateOrNew(h) - if err != nil { + key := makeAccountKey(h) + si := ic.DAO.GetStorageItem(c.Hash, key) + if si == nil { + si = new(state.StorageItem) + } + if err := c.incBalance(ic, h, si, amount); err != nil { panic(err) } - if err := c.incBalance(ic, acc, amount); err != nil { - panic(err) - } - if err := ic.DAO.PutAccountState(acc); err != nil { + if err := ic.DAO.PutStorageItem(c.Hash, key, si); err != nil { panic(err) } supply := c.getTotalSupply(ic) supply.Add(supply, amount) - err = c.saveTotalSupply(ic, supply) + err := c.saveTotalSupply(ic, supply) if err != nil { panic(err) } diff --git a/pkg/core/state/account.go b/pkg/core/state/account.go index f18a25d15..4d163b03c 100644 --- a/pkg/core/state/account.go +++ b/pkg/core/state/account.go @@ -33,8 +33,6 @@ type Account struct { ScriptHash util.Uint160 IsFrozen bool Votes []*keys.PublicKey - GAS NEP5BalanceState - NEO NEOBalanceState Balances map[util.Uint256][]UnspentBalance Unclaimed UnclaimedBalances } @@ -57,8 +55,6 @@ func (s *Account) DecodeBinary(br *io.BinReader) { br.ReadBytes(s.ScriptHash[:]) s.IsFrozen = br.ReadBool() br.ReadArray(&s.Votes) - s.GAS.DecodeBinary(br) - s.NEO.DecodeBinary(br) s.Balances = make(map[util.Uint256][]UnspentBalance) lenBalances := br.ReadVarUint() @@ -84,8 +80,6 @@ func (s *Account) EncodeBinary(bw *io.BinWriter) { bw.WriteBytes(s.ScriptHash[:]) bw.WriteBool(s.IsFrozen) bw.WriteArray(s.Votes) - s.GAS.EncodeBinary(bw) - s.NEO.EncodeBinary(bw) bw.WriteVarUint(uint64(len(s.Balances))) for k, v := range s.Balances { diff --git a/pkg/core/state/native_state.go b/pkg/core/state/native_state.go index 9744cc754..5427e3621 100644 --- a/pkg/core/state/native_state.go +++ b/pkg/core/state/native_state.go @@ -18,6 +18,30 @@ type NEOBalanceState struct { BalanceHeight uint32 } +// NEP5BalanceStateFromBytes converts serialized NEP5BalanceState to structure. +func NEP5BalanceStateFromBytes(b []byte) (*NEP5BalanceState, error) { + balance := new(NEP5BalanceState) + if len(b) == 0 { + return balance, nil + } + r := io.NewBinReaderFromBuf(b) + balance.DecodeBinary(r) + if r.Err != nil { + return nil, r.Err + } + return balance, nil +} + +// Bytes returns serialized NEP5BalanceState. +func (s *NEP5BalanceState) Bytes() []byte { + w := io.NewBufBinWriter() + s.EncodeBinary(w.BinWriter) + if w.Err != nil { + panic(w.Err) + } + return w.Bytes() +} + // EncodeBinary implements io.Serializable interface. func (s *NEP5BalanceState) EncodeBinary(w *io.BinWriter) { w.WriteVarBytes(emit.IntToBytes(&s.Balance)) @@ -32,6 +56,31 @@ func (s *NEP5BalanceState) DecodeBinary(r *io.BinReader) { s.Balance = *emit.BytesToInt(buf) } +// NEOBalanceStateFromBytes converts serialized NEOBalanceState to structure. +func NEOBalanceStateFromBytes(b []byte) (*NEOBalanceState, error) { + balance := new(NEOBalanceState) + if len(b) == 0 { + return balance, nil + } + r := io.NewBinReaderFromBuf(b) + balance.DecodeBinary(r) + + if r.Err != nil { + return nil, r.Err + } + return balance, nil +} + +// Bytes returns serialized NEOBalanceState. +func (s *NEOBalanceState) Bytes() []byte { + w := io.NewBufBinWriter() + s.EncodeBinary(w.BinWriter) + if w.Err != nil { + panic(w.Err) + } + return w.Bytes() +} + // EncodeBinary implements io.Serializable interface. func (s *NEOBalanceState) EncodeBinary(w *io.BinWriter) { s.NEP5BalanceState.EncodeBinary(w) From 3476a18fa9fccda04ebbaeb6b6d3a87c0ae8fb9d Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 26 Apr 2020 00:23:30 +0300 Subject: [PATCH 06/13] core/native: store validators in NEO native contract state This technically breaks voting with UTXO-based NEO (processTXWithValidators*), but we're moving towards the new system. --- pkg/core/blockchain.go | 6 +- pkg/core/dao/dao.go | 15 +++- pkg/core/native/native_neo.go | 149 +++++++++++++++++++++++----------- 3 files changed, 118 insertions(+), 52 deletions(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index c3a1034c0..d5b44b7b5 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -861,9 +861,9 @@ func processTXWithValidatorsSubtract(output *transaction.Output, account *state. // modAccountVotes adds given value to given account voted validators. func modAccountVotes(account *state.Account, dao *dao.Cached, value util.Fixed8) error { - if err := native.ModifyAccountVotes(account, dao, value); err != nil { - return err - } + // if err := native.ModifyAccountVotes(account, dao, value); err != nil { + // return err + // } if len(account.Votes) > 0 { vc, err := dao.GetValidatorsCount() if err != nil { diff --git a/pkg/core/dao/dao.go b/pkg/core/dao/dao.go index 34b729636..7ada3828d 100644 --- a/pkg/core/dao/dao.go +++ b/pkg/core/dao/dao.go @@ -37,6 +37,7 @@ type DAO interface { GetNextBlockValidators() (keys.PublicKeys, error) GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error) + GetStorageItemsWithPrefix(hash util.Uint160, prefix []byte) (map[string]*state.StorageItem, error) GetTransaction(hash util.Uint256) (*transaction.Transaction, uint32, error) GetUnspentCoinState(hash util.Uint256) (*state.UnspentCoin, error) GetValidatorState(publicKey *keys.PublicKey) (*state.Validator, error) @@ -471,9 +472,19 @@ func (dao *Simple) DeleteStorageItem(scripthash util.Uint160, key []byte) error // GetStorageItems returns all storage items for a given scripthash. func (dao *Simple) GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error) { + return dao.GetStorageItemsWithPrefix(hash, nil) +} + +// GetStorageItemsWithPrefix returns all storage items with given prefix for a +// given scripthash. +func (dao *Simple) GetStorageItemsWithPrefix(hash util.Uint160, prefix []byte) (map[string]*state.StorageItem, error) { var siMap = make(map[string]*state.StorageItem) var err error + lookupKey := storage.AppendPrefix(storage.STStorage, hash.BytesLE()) + if prefix != nil { + lookupKey = append(lookupKey, prefix...) + } saveToMap := func(k, v []byte) { if err != nil { return @@ -487,9 +498,9 @@ func (dao *Simple) GetStorageItems(hash util.Uint160) (map[string]*state.Storage } // Cut prefix and hash. - siMap[string(k[21:])] = si + siMap[string(k[len(lookupKey):])] = si } - dao.Store.Seek(storage.AppendPrefix(storage.STStorage, hash.BytesLE()), saveToMap) + dao.Store.Seek(lookupKey, saveToMap) if err != nil { return nil, err } diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 7eda3bd22..a70fe04d0 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -9,7 +9,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/core/state" - "github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" @@ -24,10 +23,37 @@ type NEO struct { GAS *GAS } -const neoSyscallName = "Neo.Native.Tokens.NEO" +// keyWithVotes is a serialized key with votes balance. It's not deserialized +// because some uses of it imply serialized-only usage and converting to +// PublicKey is quite expensive. +type keyWithVotes struct { + Key string + Votes *big.Int +} -// NEOTotalSupply is the total amount of NEO in the system. -const NEOTotalSupply = 100000000 +// pkeyWithVotes is a deserialized key with votes balance. +type pkeyWithVotes struct { + Key *keys.PublicKey + Votes *big.Int +} + +const ( + neoSyscallName = "Neo.Native.Tokens.NEO" + // NEOTotalSupply is the total amount of NEO in the system. + NEOTotalSupply = 100000000 + // prefixValidator is a prefix used to store validator's data. + prefixValidator = 33 +) + +// makeValidatorKey creates a key from account script hash. +func makeValidatorKey(key *keys.PublicKey) []byte { + b := key.Bytes() + // Don't create a new buffer. + b = append(b, 0) + copy(b[1:], b[0:]) + b[0] = prefixValidator + return b +} // NewNEO returns NEO native contract. func NewNEO() *NEO { @@ -57,7 +83,7 @@ func NewNEO() *NEO { n.AddMethod(md, desc, false) desc = newDescriptor("getRegisteredValidators", smartcontract.ArrayType) - md = newMethodAndPrice(n.getRegisteredValidators, 1, smartcontract.NoneFlag) + md = newMethodAndPrice(n.getRegisteredValidatorsCall, 1, smartcontract.NoneFlag) n.AddMethod(md, desc, true) desc = newDescriptor("getValidators", smartcontract.ArrayType) @@ -164,11 +190,17 @@ func (n *NEO) registerValidator(ic *interop.Context, args []vm.StackItem) vm.Sta } func (n *NEO) registerValidatorInternal(ic *interop.Context, pub *keys.PublicKey) error { - _, err := ic.DAO.GetValidatorState(pub) - if err == nil { - return err + key := makeValidatorKey(pub) + si := ic.DAO.GetStorageItem(n.Hash, key) + if si != nil { + return errors.New("already registered") } - return ic.DAO.PutValidatorState(&state.Validator{PublicKey: pub}) + si = new(state.StorageItem) + // It's the same simple counter, calling it `Votes` instead of `Balance` + // doesn't help a lot. + votes := state.NEP5BalanceState{} + si.Value = votes.Bytes() + return ic.DAO.PutStorageItem(n.Hash, key, si) } func (n *NEO) vote(ic *interop.Context, args []vm.StackItem) vm.StackItem { @@ -210,19 +242,15 @@ func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pubs keys.Public if err != nil { return err } - balance := util.Fixed8(acc.Balance.Int64()) - if err := ModifyAccountVotes(oldAcc, ic.DAO, -balance); err != nil { + if err := n.ModifyAccountVotes(oldAcc, ic.DAO, new(big.Int).Neg(&acc.Balance)); err != nil { return err } pubs = pubs.Unique() + // Check validators registration. var newPubs keys.PublicKeys for _, pub := range pubs { - _, err := ic.DAO.GetValidatorState(pub) - if err != nil { - if err == storage.ErrKeyNotFound { - continue - } - return err + if ic.DAO.GetStorageItem(n.Hash, makeValidatorKey(pub)) == nil { + continue } newPubs = append(newPubs, pub) } @@ -232,50 +260,69 @@ func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pubs keys.Public return err } if lv > 0 { - vc[lv-1] -= balance + vc[lv-1] -= util.Fixed8(acc.Balance.Int64()) } if len(newPubs) > 0 { - vc[lp-1] += balance + vc[lp-1] += util.Fixed8(acc.Balance.Int64()) } if err := ic.DAO.PutValidatorsCount(vc); err != nil { return err } } oldAcc.Votes = newPubs - if err := ModifyAccountVotes(oldAcc, ic.DAO, balance); err != nil { + if err := n.ModifyAccountVotes(oldAcc, ic.DAO, &acc.Balance); err != nil { return err } return ic.DAO.PutAccountState(oldAcc) } // ModifyAccountVotes modifies votes of the specified account by value (can be negative). -func ModifyAccountVotes(acc *state.Account, d dao.DAO, value util.Fixed8) error { +func (n *NEO) ModifyAccountVotes(acc *state.Account, d dao.DAO, value *big.Int) error { for _, vote := range acc.Votes { - validator, err := d.GetValidatorStateOrNew(vote) + key := makeValidatorKey(vote) + si := d.GetStorageItem(n.Hash, key) + if si == nil { + return errors.New("invalid validator") + } + votes, err := state.NEP5BalanceStateFromBytes(si.Value) if err != nil { return err } - validator.Votes += value - if validator.UnregisteredAndHasNoVotes() { - if err := d.DeleteValidatorState(validator); err != nil { - return err - } - } else { - if err := d.PutValidatorState(validator); err != nil { - return err - } + votes.Balance.Add(&votes.Balance, value) + si.Value = votes.Bytes() + if err := d.PutStorageItem(n.Hash, key, si); err != nil { + return err } } return nil } -func (n *NEO) getRegisteredValidators(ic *interop.Context, _ []vm.StackItem) vm.StackItem { - vs := ic.DAO.GetValidators() - arr := make([]vm.StackItem, len(vs)) - for i := range vs { +func (n *NEO) getRegisteredValidators(d dao.DAO) ([]keyWithVotes, error) { + siMap, err := d.GetStorageItemsWithPrefix(n.Hash, []byte{prefixValidator}) + if err != nil { + return nil, err + } + arr := make([]keyWithVotes, 0, len(siMap)) + for key, si := range siMap { + votes, err := state.NEP5BalanceStateFromBytes(si.Value) + if err != nil { + return nil, err + } + arr = append(arr, keyWithVotes{key, &votes.Balance}) + } + return arr, nil +} + +func (n *NEO) getRegisteredValidatorsCall(ic *interop.Context, _ []vm.StackItem) vm.StackItem { + validators, err := n.getRegisteredValidators(ic.DAO) + if err != nil { + panic(err) + } + arr := make([]vm.StackItem, len(validators)) + for i := range validators { arr[i] = vm.NewStructItem([]vm.StackItem{ - vm.NewByteArrayItem(vs[i].PublicKey.Bytes()), - vm.NewBigIntegerItem(big.NewInt(int64(vs[i].Votes))), + vm.NewByteArrayItem([]byte(validators[i].Key)), + vm.NewBigIntegerItem(validators[i].Votes), }) } return vm.NewArrayItem(arr) @@ -294,18 +341,26 @@ func (n *NEO) GetValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) ([] return sb, nil } - validators := d.GetValidators() - sort.Slice(validators, func(i, j int) bool { - // Unregistered validators go to the end of the list. - if validators[i].Registered != validators[j].Registered { - return validators[i].Registered + validatorsBytes, err := n.getRegisteredValidators(d) + if err != nil { + return nil, err + } + validators := make([]pkeyWithVotes, len(validatorsBytes)) + for i := range validatorsBytes { + validators[i].Key, err = keys.NewPublicKeyFromBytes([]byte(validatorsBytes[i].Key)) + if err != nil { + return nil, err } + validators[i].Votes = validatorsBytes[i].Votes + } + sort.Slice(validators, func(i, j int) bool { // The most-voted validators should end up in the front of the list. - if validators[i].Votes != validators[j].Votes { - return validators[i].Votes > validators[j].Votes + cmp := validators[i].Votes.Cmp(validators[j].Votes) + if cmp != 0 { + return cmp > 0 } // Ties are broken with public keys. - return validators[i].PublicKey.Cmp(validators[j].PublicKey) == -1 + return validators[i].Key.Cmp(validators[j].Key) == -1 }) count := validatorsCount.GetWeightedAverage() @@ -320,8 +375,8 @@ func (n *NEO) GetValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) ([] uniqueSBValidators := standByValidators.Unique() result := keys.PublicKeys{} for _, validator := range validators { - if validator.RegisteredAndHasVotes() || uniqueSBValidators.Contains(validator.PublicKey) { - result = append(result, validator.PublicKey) + if validator.Votes.Sign() > 0 || uniqueSBValidators.Contains(validator.Key) { + result = append(result, validator.Key) } } From 064636768b805bc855e2d305dd267a48fd7e43a0 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 26 Apr 2020 10:15:59 +0300 Subject: [PATCH 07/13] core/native: move ValidatorsCount processing into native NEO contract --- pkg/core/blockchain.go | 48 +------------ pkg/core/dao/dao.go | 20 ------ pkg/core/native/native_neo.go | 42 +++++++---- pkg/core/native/validators_count.go | 106 ++++++++++++++++++++++++++++ pkg/core/state/validator.go | 69 ------------------ pkg/core/storage/store.go | 35 +++++---- pkg/core/storage/store_test.go | 2 - 7 files changed, 154 insertions(+), 168 deletions(-) create mode 100644 pkg/core/native/validators_count.go diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index d5b44b7b5..8d4df662e 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -523,9 +523,6 @@ func (bc *Blockchain) storeBlock(block *block.Block) error { if err != nil { return err } - if err = processTXWithValidatorsSubtract(prevTXOutput, account, cache); err != nil { - return err - } } balancesLen := len(account.Balances[prevTXOutput.AssetID]) @@ -838,42 +835,6 @@ func processOutputs(tx *transaction.Transaction, dao *dao.Cached) error { if err = dao.PutAccountState(account); err != nil { return err } - if err = processTXWithValidatorsAdd(&output, account, dao); err != nil { - return err - } - } - return nil -} - -func processTXWithValidatorsAdd(output *transaction.Output, account *state.Account, dao *dao.Cached) error { - 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 *dao.Cached) error { - if output.AssetID.Equals(GoverningTokenID()) && len(account.Votes) > 0 { - return modAccountVotes(account, dao, -output.Amount) - } - return nil -} - -// modAccountVotes adds given value to given account voted validators. -func modAccountVotes(account *state.Account, dao *dao.Cached, value util.Fixed8) error { - // if err := native.ModifyAccountVotes(account, dao, value); err != nil { - // return err - // } - if len(account.Votes) > 0 { - vc, err := dao.GetValidatorsCount() - if err != nil { - return err - } - vc[len(account.Votes)-1] += value - err = dao.PutValidatorsCount(vc) - if err != nil { - return err - } } return nil } @@ -1348,7 +1309,7 @@ func (bc *Blockchain) verifyTx(t *transaction.Transaction, block *block.Block) e if err != nil { return err } - if len(votes) > state.MaxValidatorsVoted { + if len(votes) > native.MaxValidatorsVoted { return errors.New("voting candidate limit exceeded") } hash, err := util.Uint160DecodeBytesBE(desc.Key) @@ -1690,9 +1651,6 @@ func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.P if err := cache.PutAccountState(accountState); err != nil { return nil, err } - if err = processTXWithValidatorsAdd(&output, accountState, cache); err != nil { - return nil, err - } } // group inputs by the same previous hash and iterate through inputs @@ -1715,10 +1673,6 @@ func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.P return nil, err } - // process account state votes: if there are any -> validators will be updated. - if err = processTXWithValidatorsSubtract(prevOutput, accountState, cache); err != nil { - return nil, err - } delete(accountState.Balances, prevOutput.AssetID) if err = cache.PutAccountState(accountState); err != nil { return nil, err diff --git a/pkg/core/dao/dao.go b/pkg/core/dao/dao.go index 7ada3828d..4618808c9 100644 --- a/pkg/core/dao/dao.go +++ b/pkg/core/dao/dao.go @@ -43,7 +43,6 @@ type DAO interface { GetValidatorState(publicKey *keys.PublicKey) (*state.Validator, error) GetValidatorStateOrNew(publicKey *keys.PublicKey) (*state.Validator, error) GetValidators() []*state.Validator - GetValidatorsCount() (*state.ValidatorsCount, error) GetVersion() (string, error) GetWrapped() DAO HasTransaction(hash util.Uint256) bool @@ -61,7 +60,6 @@ type DAO interface { PutStorageItem(scripthash util.Uint160, key []byte, si *state.StorageItem) error PutUnspentCoinState(hash util.Uint256, ucs *state.UnspentCoin) error PutValidatorState(vs *state.Validator) error - PutValidatorsCount(vc *state.ValidatorsCount) error PutVersion(v string) error StoreAsBlock(block *block.Block, sysFee uint32) error StoreAsCurrentBlock(block *block.Block) error @@ -396,24 +394,6 @@ func (dao *Simple) DeleteValidatorState(vs *state.Validator) error { return dao.Store.Delete(key) } -// GetValidatorsCount returns current ValidatorsCount or new one if there is none -// in the DB. -func (dao *Simple) GetValidatorsCount() (*state.ValidatorsCount, error) { - vc := &state.ValidatorsCount{} - key := []byte{byte(storage.IXValidatorsCount)} - err := dao.GetAndDecode(vc, key) - if err != nil && err != storage.ErrKeyNotFound { - return nil, err - } - return vc, nil -} - -// PutValidatorsCount put given ValidatorsCount in the store. -func (dao *Simple) PutValidatorsCount(vc *state.ValidatorsCount) error { - key := []byte{byte(storage.IXValidatorsCount)} - return dao.Put(vc, key) -} - // -- end validator. // -- start notification event. diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index a70fe04d0..9e480e0ff 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -45,6 +45,15 @@ const ( prefixValidator = 33 ) +var ( + // validatorsCountKey is a key used to store validators count + // used to determine the real number of validators. + validatorsCountKey = []byte{15} + // nextValidatorsKey is a key used to store validators for the + // next block. + nextValidatorsKey = []byte{14} +) + // makeValidatorKey creates a key from account script hash. func makeValidatorKey(key *keys.PublicKey) []byte { b := key.Bytes() @@ -102,6 +111,8 @@ func NewNEO() *NEO { // Initialize initializes NEO contract. func (n *NEO) Initialize(ic *interop.Context) error { + var si state.StorageItem + if err := n.nep5TokenNative.Initialize(ic); err != nil { return err } @@ -110,6 +121,11 @@ func (n *NEO) Initialize(ic *interop.Context) error { return errors.New("already initialized") } + vc := new(ValidatorsCount) + si.Value = vc.Bytes() + if err := ic.DAO.PutStorageItem(n.Hash, validatorsCountKey, &si); err != nil { + return err + } h, vs, err := getStandbyValidatorsHash(ic) if err != nil { return err @@ -255,17 +271,22 @@ func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pubs keys.Public newPubs = append(newPubs, pub) } if lp, lv := len(newPubs), len(oldAcc.Votes); lp != lv { - vc, err := ic.DAO.GetValidatorsCount() + si := ic.DAO.GetStorageItem(n.Hash, validatorsCountKey) + if si == nil { + return errors.New("validators count uninitialized") + } + vc, err := ValidatorsCountFromBytes(si.Value) if err != nil { return err } if lv > 0 { - vc[lv-1] -= util.Fixed8(acc.Balance.Int64()) + vc[lv-1].Sub(&vc[lv-1], &acc.Balance) } if len(newPubs) > 0 { - vc[lp-1] += util.Fixed8(acc.Balance.Int64()) + vc[lp-1].Add(&vc[lp-1], &acc.Balance) } - if err := ic.DAO.PutValidatorsCount(vc); err != nil { + si.Value = vc.Bytes() + if err := ic.DAO.PutStorageItem(n.Hash, validatorsCountKey, si); err != nil { return err } } @@ -330,17 +351,14 @@ func (n *NEO) getRegisteredValidatorsCall(ic *interop.Context, _ []vm.StackItem) // GetValidatorsInternal returns a list of current validators. func (n *NEO) GetValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) ([]*keys.PublicKey, error) { - validatorsCount, err := d.GetValidatorsCount() + si := d.GetStorageItem(n.Hash, validatorsCountKey) + if si == nil { + return nil, errors.New("validators count uninitialized") + } + validatorsCount, err := ValidatorsCountFromBytes(si.Value) if err != nil { return nil, err - } else if len(validatorsCount) == 0 { - sb, err := bc.GetStandByValidators() - if err != nil { - return nil, err - } - return sb, nil } - validatorsBytes, err := n.getRegisteredValidators(d) if err != nil { return nil, err diff --git a/pkg/core/native/validators_count.go b/pkg/core/native/validators_count.go new file mode 100644 index 000000000..25cfe3c7c --- /dev/null +++ b/pkg/core/native/validators_count.go @@ -0,0 +1,106 @@ +package native + +import ( + "math/big" + + "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/vm/emit" +) + +// MaxValidatorsVoted limits the number of validators that one can vote for. +const MaxValidatorsVoted = 1024 + +// ValidatorsCount represents votes with particular number of consensus nodes +// for this number to be changeable by the voting system. +type ValidatorsCount [MaxValidatorsVoted]big.Int + +// ValidatorsCountFromBytes converts serialized ValidatorsCount to structure. +func ValidatorsCountFromBytes(b []byte) (*ValidatorsCount, error) { + vc := new(ValidatorsCount) + if len(b) == 0 { + return vc, nil + } + r := io.NewBinReaderFromBuf(b) + vc.DecodeBinary(r) + + if r.Err != nil { + return nil, r.Err + } + return vc, nil +} + +// Bytes returns serialized ValidatorsCount. +func (vc *ValidatorsCount) Bytes() []byte { + w := io.NewBufBinWriter() + vc.EncodeBinary(w.BinWriter) + if w.Err != nil { + panic(w.Err) + } + return w.Bytes() +} + +// EncodeBinary implements io.Serializable interface. +func (vc *ValidatorsCount) EncodeBinary(w *io.BinWriter) { + for i := range vc { + w.WriteVarBytes(emit.IntToBytes(&vc[i])) + } +} + +// DecodeBinary implements io.Serializable interface. +func (vc *ValidatorsCount) DecodeBinary(r *io.BinReader) { + for i := range vc { + buf := r.ReadVarBytes() + if r.Err != nil { + return + } + vc[i] = *emit.BytesToInt(buf) + } +} + +// GetWeightedAverage returns an average count of validators that's been voted +// for not counting 1/4 of minimum and maximum numbers. +func (vc *ValidatorsCount) GetWeightedAverage() int { + const ( + lowerThreshold = 0.25 + upperThreshold = 0.75 + ) + var ( + sumWeight, sumValue, overallSum, slidingSum int64 + slidingRatio float64 + ) + + for i := range vc { + overallSum += vc[i].Int64() + } + + for i := range vc { + if slidingRatio >= upperThreshold { + break + } + weight := vc[i].Int64() + slidingSum += weight + previousRatio := slidingRatio + slidingRatio = float64(slidingSum) / float64(overallSum) + + if slidingRatio <= lowerThreshold { + continue + } + + if previousRatio < lowerThreshold { + if slidingRatio > upperThreshold { + weight = int64((upperThreshold - lowerThreshold) * float64(overallSum)) + } else { + weight = int64((slidingRatio - lowerThreshold) * float64(overallSum)) + } + } else if slidingRatio > upperThreshold { + weight = int64((upperThreshold - previousRatio) * float64(overallSum)) + } + sumWeight += weight + // Votes with N values get stored with N-1 index, thus +1 here. + sumValue += (int64(i) + 1) * weight + } + if sumValue == 0 || sumWeight == 0 { + return 0 + } + return int(sumValue / sumWeight) +} diff --git a/pkg/core/state/validator.go b/pkg/core/state/validator.go index 2a2d65fa1..ecdc8cc67 100644 --- a/pkg/core/state/validator.go +++ b/pkg/core/state/validator.go @@ -6,9 +6,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/util" ) -// MaxValidatorsVoted limits the number of validators that one can vote for. -const MaxValidatorsVoted = 1024 - // Validator holds the state of a validator. type Validator struct { PublicKey *keys.PublicKey @@ -16,10 +13,6 @@ type Validator struct { Votes util.Fixed8 } -// ValidatorsCount represents votes with particular number of consensus nodes -// for this number to be changeable by the voting system. -type ValidatorsCount [MaxValidatorsVoted]util.Fixed8 - // RegisteredAndHasVotes returns true or false whether Validator is registered and has votes. func (vs *Validator) RegisteredAndHasVotes() bool { return vs.Registered && vs.Votes > util.Fixed8(0) @@ -44,65 +37,3 @@ func (vs *Validator) DecodeBinary(reader *io.BinReader) { vs.Registered = reader.ReadBool() vs.Votes.DecodeBinary(reader) } - -// EncodeBinary encodes ValidatorCount to the given BinWriter. -func (vc *ValidatorsCount) EncodeBinary(w *io.BinWriter) { - for i := range vc { - vc[i].EncodeBinary(w) - } -} - -// DecodeBinary decodes ValidatorCount from the given BinReader. -func (vc *ValidatorsCount) DecodeBinary(r *io.BinReader) { - for i := range vc { - vc[i].DecodeBinary(r) - } -} - -// GetWeightedAverage returns an average count of validators that's been voted -// for not counting 1/4 of minimum and maximum numbers. -func (vc *ValidatorsCount) GetWeightedAverage() int { - const ( - lowerThreshold = 0.25 - upperThreshold = 0.75 - ) - var ( - sumWeight, sumValue, overallSum, slidingSum util.Fixed8 - slidingRatio float64 - ) - - for i := range vc { - overallSum += vc[i] - } - - for i := range vc { - if slidingRatio >= upperThreshold { - break - } - weight := vc[i] - slidingSum += weight - previousRatio := slidingRatio - slidingRatio = slidingSum.FloatValue() / overallSum.FloatValue() - - if slidingRatio <= lowerThreshold { - continue - } - - if previousRatio < lowerThreshold { - if slidingRatio > upperThreshold { - weight = util.Fixed8FromFloat((upperThreshold - lowerThreshold) * overallSum.FloatValue()) - } else { - weight = util.Fixed8FromFloat((slidingRatio - lowerThreshold) * overallSum.FloatValue()) - } - } else if slidingRatio > upperThreshold { - weight = util.Fixed8FromFloat((upperThreshold - previousRatio) * overallSum.FloatValue()) - } - sumWeight += weight - // Votes with N values get stored with N-1 index, thus +1 here. - sumValue += util.Fixed8(i+1) * weight - } - if sumValue == 0 || sumWeight == 0 { - return 0 - } - return int(sumValue / sumWeight) -} diff --git a/pkg/core/storage/store.go b/pkg/core/storage/store.go index ea48dc0b8..6726ff58c 100644 --- a/pkg/core/storage/store.go +++ b/pkg/core/storage/store.go @@ -7,24 +7,23 @@ import ( // KeyPrefix constants. const ( - DataBlock KeyPrefix = 0x01 - DataTransaction KeyPrefix = 0x02 - STAccount KeyPrefix = 0x40 - STCoin KeyPrefix = 0x44 - STSpentCoin KeyPrefix = 0x45 - STNextValidators KeyPrefix = 0x47 - STValidator KeyPrefix = 0x48 - STAsset KeyPrefix = 0x4c - STNotification KeyPrefix = 0x4d - STContract KeyPrefix = 0x50 - STStorage KeyPrefix = 0x70 - STNEP5Transfers KeyPrefix = 0x72 - STNEP5Balances KeyPrefix = 0x73 - IXHeaderHashList KeyPrefix = 0x80 - IXValidatorsCount KeyPrefix = 0x90 - SYSCurrentBlock KeyPrefix = 0xc0 - SYSCurrentHeader KeyPrefix = 0xc1 - SYSVersion KeyPrefix = 0xf0 + DataBlock KeyPrefix = 0x01 + DataTransaction KeyPrefix = 0x02 + STAccount KeyPrefix = 0x40 + STCoin KeyPrefix = 0x44 + STSpentCoin KeyPrefix = 0x45 + STNextValidators KeyPrefix = 0x47 + STValidator KeyPrefix = 0x48 + STAsset KeyPrefix = 0x4c + STNotification KeyPrefix = 0x4d + STContract KeyPrefix = 0x50 + STStorage KeyPrefix = 0x70 + STNEP5Transfers KeyPrefix = 0x72 + STNEP5Balances KeyPrefix = 0x73 + IXHeaderHashList KeyPrefix = 0x80 + SYSCurrentBlock KeyPrefix = 0xc0 + SYSCurrentHeader KeyPrefix = 0xc1 + SYSVersion KeyPrefix = 0xf0 ) // ErrKeyNotFound is an error returned by Store implementations diff --git a/pkg/core/storage/store_test.go b/pkg/core/storage/store_test.go index 4c028fbe4..9c9f6ef70 100644 --- a/pkg/core/storage/store_test.go +++ b/pkg/core/storage/store_test.go @@ -17,7 +17,6 @@ var ( STContract, STStorage, IXHeaderHashList, - IXValidatorsCount, SYSCurrentBlock, SYSCurrentHeader, SYSVersion, @@ -33,7 +32,6 @@ var ( 0x50, 0x70, 0x80, - 0x90, 0xc0, 0xc1, 0xf0, From 36c6c6690b19f2b75a4a4e1ed522ee2adb3e05f0 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 26 Apr 2020 12:51:17 +0300 Subject: [PATCH 08/13] native: distribute GAS even for empty transfers As it's one of the use cases. --- pkg/core/native/native_neo.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 9e480e0ff..c77e679fe 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -158,14 +158,15 @@ func (n *NEO) increaseBalance(ic *interop.Context, h util.Uint160, si *state.Sto if err != nil { return err } - if sign := amount.Sign(); sign == 0 { - return nil - } else if sign == -1 && acc.Balance.Cmp(new(big.Int).Neg(amount)) == -1 { + if amount.Sign() == -1 && acc.Balance.Cmp(new(big.Int).Neg(amount)) == -1 { return errors.New("insufficient funds") } if err := n.distributeGas(ic, h, acc); err != nil { return err } + if amount.Sign() == 0 { + return nil + } acc.Balance.Add(&acc.Balance, amount) si.Value = acc.Bytes() return nil From 66c80d429ef5de8823655dad6d03d84cd9ca802d Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 26 Apr 2020 13:00:17 +0300 Subject: [PATCH 09/13] native: update voting information when changing Neo balance --- pkg/core/native/native_neo.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index c77e679fe..9ce5523d2 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -167,6 +167,27 @@ func (n *NEO) increaseBalance(ic *interop.Context, h util.Uint160, si *state.Sto if amount.Sign() == 0 { return nil } + oldAcc, err := ic.DAO.GetAccountState(h) + if err != nil { + return err + } + if err := n.ModifyAccountVotes(oldAcc, ic.DAO, new(big.Int).Neg(&acc.Balance)); err != nil { + return err + } + siVC := ic.DAO.GetStorageItem(n.Hash, validatorsCountKey) + if siVC == nil { + return errors.New("validators count uninitialized") + } + vc, err := ValidatorsCountFromBytes(siVC.Value) + if err != nil { + return err + } + vc[len(oldAcc.Votes)-1].Add(&vc[len(oldAcc.Votes)-1], amount) + siVC.Value = vc.Bytes() + if err := ic.DAO.PutStorageItem(n.Hash, validatorsCountKey, siVC); err != nil { + return err + } + acc.Balance.Add(&acc.Balance, amount) si.Value = acc.Bytes() return nil From bc4a6a6babee07f46561cf50b22c6aa2e535fd9d Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 26 Apr 2020 13:42:05 +0300 Subject: [PATCH 10/13] core/native: move NextValidators storage to NEO contract --- pkg/core/dao/dao.go | 34 --------------------- pkg/core/native/native_neo.go | 57 ++++++++++++++++++----------------- pkg/core/storage/store.go | 1 - pkg/crypto/keys/publickey.go | 10 ++++++ 4 files changed, 40 insertions(+), 62 deletions(-) diff --git a/pkg/core/dao/dao.go b/pkg/core/dao/dao.go index 4618808c9..23fab4fc8 100644 --- a/pkg/core/dao/dao.go +++ b/pkg/core/dao/dao.go @@ -34,7 +34,6 @@ type DAO interface { GetHeaderHashes() ([]util.Uint256, error) GetNEP5Balances(acc util.Uint160) (*state.NEP5Balances, error) GetNEP5TransferLog(acc util.Uint160, index uint32) (*state.NEP5TransferLog, error) - GetNextBlockValidators() (keys.PublicKeys, error) GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error) GetStorageItemsWithPrefix(hash util.Uint160, prefix []byte) (map[string]*state.StorageItem, error) @@ -56,7 +55,6 @@ type DAO interface { PutCurrentHeader(hashAndIndex []byte) error PutNEP5Balances(acc util.Uint160, bs *state.NEP5Balances) error PutNEP5TransferLog(acc util.Uint160, index uint32, lg *state.NEP5TransferLog) error - PutNextBlockValidators(keys.PublicKeys) error PutStorageItem(scripthash util.Uint160, key []byte, si *state.StorageItem) error PutUnspentCoinState(hash util.Uint256, ucs *state.UnspentCoin) error PutValidatorState(vs *state.Validator) error @@ -311,38 +309,6 @@ func (dao *Simple) putUnspentCoinState(hash util.Uint256, ucs *state.UnspentCoin // -- start validator. -// GetNextBlockValidators retrieves next block validators from store or nil if they are missing. -func (dao *Simple) GetNextBlockValidators() (keys.PublicKeys, error) { - key := []byte{byte(storage.STNextValidators)} - buf, err := dao.Store.Get(key) - if err != nil { - if err == storage.ErrKeyNotFound { - return nil, nil - } - return nil, err - } - - var pubs keys.PublicKeys - r := io.NewBinReaderFromBuf(buf) - r.ReadArray(&pubs) - if r.Err != nil { - return nil, r.Err - } - return pubs, nil -} - -// PutNextBlockValidators puts next block validators to store. -func (dao *Simple) PutNextBlockValidators(pubs keys.PublicKeys) error { - w := io.NewBufBinWriter() - w.WriteArray(pubs) - if w.Err != nil { - return w.Err - } - - key := []byte{byte(storage.STNextValidators)} - return dao.Store.Put(key, w.Bytes()) -} - // GetValidatorStateOrNew gets validator from store or created new one in case of error. func (dao *Simple) GetValidatorStateOrNew(publicKey *keys.PublicKey) (*state.Validator, error) { validatorState, err := dao.GetValidatorState(publicKey) diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 9ce5523d2..3f7400dd0 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -147,10 +147,9 @@ func (n *NEO) OnPersist(ic *interop.Context) error { if err != nil { return err } - if err := ic.DAO.PutNextBlockValidators(pubs); err != nil { - return err - } - return nil + si := new(state.StorageItem) + si.Value = pubs.Bytes() + return ic.DAO.PutStorageItem(n.Hash, nextValidatorsKey, si) } func (n *NEO) increaseBalance(ic *interop.Context, h util.Uint160, si *state.StorageItem, amount *big.Int) error { @@ -171,23 +170,24 @@ func (n *NEO) increaseBalance(ic *interop.Context, h util.Uint160, si *state.Sto if err != nil { return err } - if err := n.ModifyAccountVotes(oldAcc, ic.DAO, new(big.Int).Neg(&acc.Balance)); err != nil { - return err + if len(oldAcc.Votes) > 0 { + if err := n.ModifyAccountVotes(oldAcc, ic.DAO, new(big.Int).Neg(&acc.Balance)); err != nil { + return err + } + siVC := ic.DAO.GetStorageItem(n.Hash, validatorsCountKey) + if siVC == nil { + return errors.New("validators count uninitialized") + } + vc, err := ValidatorsCountFromBytes(siVC.Value) + if err != nil { + return err + } + vc[len(oldAcc.Votes)-1].Add(&vc[len(oldAcc.Votes)-1], amount) + siVC.Value = vc.Bytes() + if err := ic.DAO.PutStorageItem(n.Hash, validatorsCountKey, siVC); err != nil { + return err + } } - siVC := ic.DAO.GetStorageItem(n.Hash, validatorsCountKey) - if siVC == nil { - return errors.New("validators count uninitialized") - } - vc, err := ValidatorsCountFromBytes(siVC.Value) - if err != nil { - return err - } - vc[len(oldAcc.Votes)-1].Add(&vc[len(oldAcc.Votes)-1], amount) - siVC.Value = vc.Bytes() - if err := ic.DAO.PutStorageItem(n.Hash, validatorsCountKey, siVC); err != nil { - return err - } - acc.Balance.Add(&acc.Balance, amount) si.Value = acc.Bytes() return nil @@ -372,7 +372,7 @@ func (n *NEO) getRegisteredValidatorsCall(ic *interop.Context, _ []vm.StackItem) } // GetValidatorsInternal returns a list of current validators. -func (n *NEO) GetValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) ([]*keys.PublicKey, error) { +func (n *NEO) GetValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) { si := d.GetStorageItem(n.Hash, validatorsCountKey) if si == nil { return nil, errors.New("validators count uninitialized") @@ -450,14 +450,17 @@ func (n *NEO) getNextBlockValidators(ic *interop.Context, _ []vm.StackItem) vm.S } // GetNextBlockValidatorsInternal returns next block validators. -func (n *NEO) GetNextBlockValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) ([]*keys.PublicKey, error) { - result, err := d.GetNextBlockValidators() - if err != nil { - return nil, err - } else if result == nil { +func (n *NEO) GetNextBlockValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) { + si := d.GetStorageItem(n.Hash, nextValidatorsKey) + if si == nil { return bc.GetStandByValidators() } - return result, nil + pubs := keys.PublicKeys{} + err := pubs.DecodeBytes(si.Value) + if err != nil { + return nil, err + } + return pubs, nil } func pubsToArray(pubs keys.PublicKeys) vm.StackItem { diff --git a/pkg/core/storage/store.go b/pkg/core/storage/store.go index 6726ff58c..12a46dd69 100644 --- a/pkg/core/storage/store.go +++ b/pkg/core/storage/store.go @@ -12,7 +12,6 @@ const ( STAccount KeyPrefix = 0x40 STCoin KeyPrefix = 0x44 STSpentCoin KeyPrefix = 0x45 - STNextValidators KeyPrefix = 0x47 STValidator KeyPrefix = 0x48 STAsset KeyPrefix = 0x4c STNotification KeyPrefix = 0x4d diff --git a/pkg/crypto/keys/publickey.go b/pkg/crypto/keys/publickey.go index 063995201..d03bdb22b 100644 --- a/pkg/crypto/keys/publickey.go +++ b/pkg/crypto/keys/publickey.go @@ -35,6 +35,16 @@ func (keys *PublicKeys) DecodeBytes(data []byte) error { return b.Err } +// Bytes encodes PublicKeys to the new slice of bytes. +func (keys *PublicKeys) Bytes() []byte { + buf := io.NewBufBinWriter() + buf.WriteArray(*keys) + if buf.Err != nil { + panic(buf.Err) + } + return buf.Bytes() +} + // Contains checks whether passed param contained in PublicKeys. func (keys PublicKeys) Contains(pKey *PublicKey) bool { for _, key := range keys { From 2fa3bdf6a9c82ef37341269c00ad5872ef54ad97 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 26 Apr 2020 14:00:17 +0300 Subject: [PATCH 11/13] core/native: move Votes from account to native NEO state --- pkg/core/interop_neo.go | 18 ---------------- pkg/core/interop_neo_test.go | 14 ------------ pkg/core/interops.go | 2 -- pkg/core/interops_test.go | 1 - pkg/core/native/native_neo.go | 27 +++++++++--------------- pkg/core/state/account.go | 5 ----- pkg/core/state/account_test.go | 6 ------ pkg/core/state/native_state.go | 4 ++++ pkg/rpc/client/rpc_test.go | 2 -- pkg/rpc/response/result/account_state.go | 11 ++++------ 10 files changed, 18 insertions(+), 72 deletions(-) diff --git a/pkg/core/interop_neo.go b/pkg/core/interop_neo.go index c43b85ccb..d144891e1 100644 --- a/pkg/core/interop_neo.go +++ b/pkg/core/interop_neo.go @@ -404,24 +404,6 @@ func accountGetScriptHash(ic *interop.Context, v *vm.VM) error { return nil } -// accountGetVotes returns votes of a given account. -func accountGetVotes(ic *interop.Context, v *vm.VM) error { - accInterface := v.Estack().Pop().Value() - acc, ok := accInterface.(*state.Account) - if !ok { - return fmt.Errorf("%T is not an account state", acc) - } - if len(acc.Votes) > vm.MaxArraySize { - return errors.New("too many votes") - } - votes := make([]vm.StackItem, 0, len(acc.Votes)) - for _, key := range acc.Votes { - votes = append(votes, vm.NewByteArrayItem(key.Bytes())) - } - v.Estack().PushVal(votes) - return nil -} - // accountIsStandard checks whether given account is standard. func accountIsStandard(ic *interop.Context, v *vm.VM) error { accbytes := v.Estack().Pop().Bytes() diff --git a/pkg/core/interop_neo_test.go b/pkg/core/interop_neo_test.go index 92603efd8..d7625d681 100644 --- a/pkg/core/interop_neo_test.go +++ b/pkg/core/interop_neo_test.go @@ -417,17 +417,6 @@ func TestAccountGetScriptHash(t *testing.T) { require.Equal(t, accState.ScriptHash.BytesBE(), hash) } -func TestAccountGetVotes(t *testing.T) { - v, accState, context, chain := createVMAndAccState(t) - defer chain.Close() - v.Estack().PushVal(vm.NewInteropItem(accState)) - - err := accountGetVotes(context, v) - require.NoError(t, err) - votes := v.Estack().Pop().Value().([]vm.StackItem) - require.Equal(t, vm.NewByteArrayItem(accState.Votes[0].Bytes()), votes[0]) -} - func TestContractGetScript(t *testing.T) { v, contractState, context, chain := createVMAndContractState(t) defer chain.Close() @@ -603,9 +592,6 @@ func createVMAndAccState(t *testing.T) (*vm.VM, *state.Account, *interop.Context hash, err := util.Uint160DecodeStringBE(rawHash) accountState := state.NewAccount(hash) - key := &keys.PublicKey{X: big.NewInt(1), Y: big.NewInt(1)} - accountState.Votes = []*keys.PublicKey{key} - require.NoError(t, err) chain := newTestChain(t) context := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore()), nil, nil) diff --git a/pkg/core/interops.go b/pkg/core/interops.go index ccb040bd6..7a4d276c8 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -115,7 +115,6 @@ var systemInterops = []interop.Function{ var neoInterops = []interop.Function{ {Name: "Neo.Account.GetBalance", Func: accountGetBalance, Price: 1}, {Name: "Neo.Account.GetScriptHash", Func: accountGetScriptHash, Price: 1}, - {Name: "Neo.Account.GetVotes", Func: accountGetVotes, Price: 1}, {Name: "Neo.Account.IsStandard", Func: accountIsStandard, Price: 100}, {Name: "Neo.Asset.Create", Func: assetCreate, Price: 0}, {Name: "Neo.Asset.GetAdmin", Func: assetGetAdmin, Price: 1}, @@ -204,7 +203,6 @@ var neoInterops = []interop.Function{ // Old compatibility APIs. {Name: "AntShares.Account.GetBalance", Func: accountGetBalance, Price: 1}, {Name: "AntShares.Account.GetScriptHash", Func: accountGetScriptHash, Price: 1}, - {Name: "AntShares.Account.GetVotes", Func: accountGetVotes, Price: 1}, {Name: "AntShares.Asset.Create", Func: assetCreate, Price: 0}, {Name: "AntShares.Asset.GetAdmin", Func: assetGetAdmin, Price: 1}, {Name: "AntShares.Asset.GetAmount", Func: assetGetAmount, Price: 1}, diff --git a/pkg/core/interops_test.go b/pkg/core/interops_test.go index 2f7b47b74..70dd25c8c 100644 --- a/pkg/core/interops_test.go +++ b/pkg/core/interops_test.go @@ -34,7 +34,6 @@ func TestUnexpectedNonInterops(t *testing.T) { funcs := []func(*interop.Context, *vm.VM) error{ accountGetBalance, accountGetScriptHash, - accountGetVotes, assetGetAdmin, assetGetAmount, assetGetAssetID, diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 3f7400dd0..41c5a5c6c 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -166,12 +166,8 @@ func (n *NEO) increaseBalance(ic *interop.Context, h util.Uint160, si *state.Sto if amount.Sign() == 0 { return nil } - oldAcc, err := ic.DAO.GetAccountState(h) - if err != nil { - return err - } - if len(oldAcc.Votes) > 0 { - if err := n.ModifyAccountVotes(oldAcc, ic.DAO, new(big.Int).Neg(&acc.Balance)); err != nil { + if len(acc.Votes) > 0 { + if err := n.ModifyAccountVotes(acc, ic.DAO, new(big.Int).Neg(&acc.Balance)); err != nil { return err } siVC := ic.DAO.GetStorageItem(n.Hash, validatorsCountKey) @@ -182,7 +178,7 @@ func (n *NEO) increaseBalance(ic *interop.Context, h util.Uint160, si *state.Sto if err != nil { return err } - vc[len(oldAcc.Votes)-1].Add(&vc[len(oldAcc.Votes)-1], amount) + vc[len(acc.Votes)-1].Add(&vc[len(acc.Votes)-1], amount) siVC.Value = vc.Bytes() if err := ic.DAO.PutStorageItem(n.Hash, validatorsCountKey, siVC); err != nil { return err @@ -276,11 +272,7 @@ func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pubs keys.Public if err != nil { return err } - oldAcc, err := ic.DAO.GetAccountState(h) - if err != nil { - return err - } - if err := n.ModifyAccountVotes(oldAcc, ic.DAO, new(big.Int).Neg(&acc.Balance)); err != nil { + if err := n.ModifyAccountVotes(acc, ic.DAO, new(big.Int).Neg(&acc.Balance)); err != nil { return err } pubs = pubs.Unique() @@ -292,7 +284,7 @@ func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pubs keys.Public } newPubs = append(newPubs, pub) } - if lp, lv := len(newPubs), len(oldAcc.Votes); lp != lv { + if lp, lv := len(newPubs), len(acc.Votes); lp != lv { si := ic.DAO.GetStorageItem(n.Hash, validatorsCountKey) if si == nil { return errors.New("validators count uninitialized") @@ -312,15 +304,16 @@ func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pubs keys.Public return err } } - oldAcc.Votes = newPubs - if err := n.ModifyAccountVotes(oldAcc, ic.DAO, &acc.Balance); err != nil { + acc.Votes = newPubs + if err := n.ModifyAccountVotes(acc, ic.DAO, &acc.Balance); err != nil { return err } - return ic.DAO.PutAccountState(oldAcc) + si.Value = acc.Bytes() + return ic.DAO.PutStorageItem(n.Hash, key, si) } // ModifyAccountVotes modifies votes of the specified account by value (can be negative). -func (n *NEO) ModifyAccountVotes(acc *state.Account, d dao.DAO, value *big.Int) error { +func (n *NEO) ModifyAccountVotes(acc *state.NEOBalanceState, d dao.DAO, value *big.Int) error { for _, vote := range acc.Votes { key := makeValidatorKey(vote) si := d.GetStorageItem(n.Hash, key) diff --git a/pkg/core/state/account.go b/pkg/core/state/account.go index 4d163b03c..2fc3a4f66 100644 --- a/pkg/core/state/account.go +++ b/pkg/core/state/account.go @@ -1,7 +1,6 @@ package state import ( - "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/util" ) @@ -32,7 +31,6 @@ type Account struct { Version uint8 ScriptHash util.Uint160 IsFrozen bool - Votes []*keys.PublicKey Balances map[util.Uint256][]UnspentBalance Unclaimed UnclaimedBalances } @@ -43,7 +41,6 @@ func NewAccount(scriptHash util.Uint160) *Account { Version: 0, ScriptHash: scriptHash, IsFrozen: false, - Votes: []*keys.PublicKey{}, Balances: make(map[util.Uint256][]UnspentBalance), Unclaimed: UnclaimedBalances{Raw: []byte{}}, } @@ -54,7 +51,6 @@ func (s *Account) DecodeBinary(br *io.BinReader) { s.Version = uint8(br.ReadB()) br.ReadBytes(s.ScriptHash[:]) s.IsFrozen = br.ReadBool() - br.ReadArray(&s.Votes) s.Balances = make(map[util.Uint256][]UnspentBalance) lenBalances := br.ReadVarUint() @@ -79,7 +75,6 @@ func (s *Account) EncodeBinary(bw *io.BinWriter) { bw.WriteB(byte(s.Version)) bw.WriteBytes(s.ScriptHash[:]) bw.WriteBool(s.IsFrozen) - bw.WriteArray(s.Votes) bw.WriteVarUint(uint64(len(s.Balances))) for k, v := range s.Balances { diff --git a/pkg/core/state/account_test.go b/pkg/core/state/account_test.go index 307abec8f..fab1049d7 100644 --- a/pkg/core/state/account_test.go +++ b/pkg/core/state/account_test.go @@ -3,7 +3,6 @@ package state import ( "testing" - "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/internal/random" "github.com/nspcc-dev/neo-go/pkg/internal/testserdes" "github.com/nspcc-dev/neo-go/pkg/util" @@ -14,7 +13,6 @@ func TestDecodeEncodeAccountState(t *testing.T) { var ( n = 10 balances = make(map[util.Uint256][]UnspentBalance) - votes = make([]*keys.PublicKey, n) ) for i := 0; i < n; i++ { asset := random.Uint256() @@ -25,16 +23,12 @@ func TestDecodeEncodeAccountState(t *testing.T) { Value: util.Fixed8(int64(random.Int(1, 10000))), }) } - k, err := keys.NewPrivateKey() - assert.Nil(t, err) - votes[i] = k.PublicKey() } a := &Account{ Version: 0, ScriptHash: random.Uint160(), IsFrozen: true, - Votes: votes, Balances: balances, Unclaimed: UnclaimedBalances{Raw: []byte{}}, } diff --git a/pkg/core/state/native_state.go b/pkg/core/state/native_state.go index 5427e3621..f1377f460 100644 --- a/pkg/core/state/native_state.go +++ b/pkg/core/state/native_state.go @@ -3,6 +3,7 @@ package state import ( "math/big" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/vm/emit" ) @@ -16,6 +17,7 @@ type NEP5BalanceState struct { type NEOBalanceState struct { NEP5BalanceState BalanceHeight uint32 + Votes keys.PublicKeys } // NEP5BalanceStateFromBytes converts serialized NEP5BalanceState to structure. @@ -85,10 +87,12 @@ func (s *NEOBalanceState) Bytes() []byte { func (s *NEOBalanceState) EncodeBinary(w *io.BinWriter) { s.NEP5BalanceState.EncodeBinary(w) w.WriteU32LE(s.BalanceHeight) + w.WriteArray(s.Votes) } // DecodeBinary implements io.Serializable interface. func (s *NEOBalanceState) DecodeBinary(r *io.BinReader) { s.NEP5BalanceState.DecodeBinary(r) s.BalanceHeight = r.ReadU32LE() + r.ReadArray(&s.Votes) } diff --git a/pkg/rpc/client/rpc_test.go b/pkg/rpc/client/rpc_test.go index f227d8138..f630868b2 100644 --- a/pkg/rpc/client/rpc_test.go +++ b/pkg/rpc/client/rpc_test.go @@ -11,7 +11,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core" "github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/transaction" - "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/rpc/request" "github.com/nspcc-dev/neo-go/pkg/rpc/response/result" @@ -49,7 +48,6 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ Version: 0, ScriptHash: scriptHash, IsFrozen: false, - Votes: []*keys.PublicKey{}, Balances: result.Balances{ result.Balance{ Asset: core.GoverningTokenID(), diff --git a/pkg/rpc/response/result/account_state.go b/pkg/rpc/response/result/account_state.go index e73b0ef55..cbff2fff9 100644 --- a/pkg/rpc/response/result/account_state.go +++ b/pkg/rpc/response/result/account_state.go @@ -5,18 +5,16 @@ import ( "sort" "github.com/nspcc-dev/neo-go/pkg/core/state" - "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/util" ) // AccountState wrapper used for the representation of // state.Account on the RPC Server. type AccountState struct { - Version uint8 `json:"version"` - ScriptHash util.Uint160 `json:"script_hash"` - IsFrozen bool `json:"frozen"` - Votes []*keys.PublicKey `json:"votes"` - Balances []Balance `json:"balances"` + Version uint8 `json:"version"` + ScriptHash util.Uint160 `json:"script_hash"` + IsFrozen bool `json:"frozen"` + Balances []Balance `json:"balances"` } // Balances type for sorting balances in rpc response. @@ -48,7 +46,6 @@ func NewAccountState(a *state.Account) AccountState { Version: a.Version, ScriptHash: a.ScriptHash, IsFrozen: a.IsFrozen, - Votes: a.Votes, Balances: balances, } } From b83e84ca0851aa334cf598e1aacc9eac19fdb45f Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 26 Apr 2020 20:04:16 +0300 Subject: [PATCH 12/13] core: switch to the new voting system (via native NEO contract) It has all the methods required now, so you can register, vote and get voting results. Fixes #865. --- pkg/consensus/consensus.go | 14 +-- pkg/core/blockchain.go | 156 ++------------------------ pkg/core/blockchainer/blockchainer.go | 4 +- pkg/core/dao/dao.go | 61 ---------- pkg/core/dao/dao_test.go | 55 --------- pkg/core/interop_neo.go | 7 -- pkg/core/interops.go | 2 - pkg/core/native/native_neo.go | 36 +++--- pkg/core/state/validator.go | 36 +----- pkg/core/state/validator_test.go | 57 ---------- pkg/network/helper_test.go | 4 +- pkg/rpc/response/result/validator.go | 3 +- pkg/rpc/server/server.go | 6 +- 13 files changed, 42 insertions(+), 399 deletions(-) delete mode 100644 pkg/core/state/validator_test.go diff --git a/pkg/consensus/consensus.go b/pkg/consensus/consensus.go index 144c18897..151160622 100644 --- a/pkg/consensus/consensus.go +++ b/pkg/consensus/consensus.go @@ -479,22 +479,12 @@ func (s *service) getVerifiedTx(count int) []block.Transaction { return res } -func (s *service) getValidators(txx ...block.Transaction) []crypto.PublicKey { +func (s *service) getValidators(_ ...block.Transaction) []crypto.PublicKey { var ( pKeys []*keys.PublicKey err error ) - if len(txx) == 0 { - pKeys, err = s.Chain.GetValidators() - } else { - ntxx := make([]*transaction.Transaction, len(txx)) - for i := range ntxx { - ntxx[i] = txx[i].(*transaction.Transaction) - } - - pKeys, err = s.Chain.GetValidators(ntxx...) - } - + pKeys, err = s.Chain.GetValidators() if err != nil { s.log.Error("error while trying to get validators", zap.Error(err)) } diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 8d4df662e..37ae27923 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -630,14 +630,6 @@ func (bc *Blockchain) storeBlock(block *block.Block) error { return err } } - case *transaction.EnrollmentTX: - if err := processEnrollmentTX(cache, t); err != nil { - return err - } - case *transaction.StateTX: - if err := bc.processStateTX(cache, tx, t); err != nil { - return err - } case *transaction.InvocationTX: systemInterop := bc.newInteropContext(trigger.Application, cache, block, tx) v := SpawnVM(systemInterop) @@ -839,26 +831,6 @@ func processOutputs(tx *transaction.Transaction, dao *dao.Cached) error { return nil } -func processValidatorStateDescriptor(descriptor *transaction.StateDescriptor, dao *dao.Cached) error { - publicKey := &keys.PublicKey{} - err := publicKey.DecodeBytes(descriptor.Key) - if err != nil { - return err - } - validatorState, err := dao.GetValidatorStateOrNew(publicKey) - if err != nil { - return err - } - if descriptor.Field == "Registered" { - if len(descriptor.Value) == 1 { - validatorState.Registered = descriptor.Value[0] != 0 - return dao.PutValidatorState(validatorState) - } - return errors.New("bad descriptor value") - } - return nil -} - func (bc *Blockchain) processAccountStateDescriptor(descriptor *transaction.StateDescriptor, t *transaction.Transaction, dao *dao.Cached) error { hash, err := util.Uint160DecodeBytesBE(descriptor.Key) if err != nil { @@ -1335,7 +1307,7 @@ func (bc *Blockchain) verifyTx(t *transaction.Transaction, block *block.Block) e for _, k := range votes { var isRegistered bool for i := range validators { - if k.Equal(validators[i].PublicKey) { + if k.Equal(validators[i].Key) { isRegistered = true break } @@ -1631,128 +1603,14 @@ func (bc *Blockchain) GetStandByValidators() (keys.PublicKeys, error) { return getValidators(bc.config) } -// GetValidators returns validators. -// Golang implementation of GetValidators method in C# (https://github.com/neo-project/neo/blob/c64748ecbac3baeb8045b16af0d518398a6ced24/neo/Persistence/Snapshot.cs#L182) -func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.PublicKey, error) { - cache := dao.NewCached(bc.dao) - if len(txes) > 0 { - for _, tx := range txes { - // iterate through outputs - for index, output := range tx.Outputs { - accountState, err := cache.GetAccountStateOrNew(output.ScriptHash) - if err != nil { - return nil, err - } - accountState.Balances[output.AssetID] = append(accountState.Balances[output.AssetID], state.UnspentBalance{ - Tx: tx.Hash(), - Index: uint16(index), - Value: output.Amount, - }) - if err := cache.PutAccountState(accountState); err != nil { - return nil, err - } - } - - // group inputs by the same previous hash and iterate through inputs - group := make(map[util.Uint256][]*transaction.Input) - for i := range tx.Inputs { - hash := tx.Inputs[i].PrevHash - group[hash] = append(group[hash], &tx.Inputs[i]) - } - - for hash, inputs := range group { - unspent, err := cache.GetUnspentCoinState(hash) - if err != nil { - return nil, err - } - // process inputs - for _, input := range inputs { - prevOutput := &unspent.States[input.PrevIndex].Output - accountState, err := cache.GetAccountStateOrNew(prevOutput.ScriptHash) - if err != nil { - return nil, err - } - - delete(accountState.Balances, prevOutput.AssetID) - if err = cache.PutAccountState(accountState); err != nil { - return nil, err - } - } - } - - switch t := tx.Data.(type) { - case *transaction.EnrollmentTX: - if err := processEnrollmentTX(cache, t); err != nil { - return nil, err - } - case *transaction.StateTX: - if err := bc.processStateTX(cache, tx, t); err != nil { - return nil, err - } - } - } - } - - return bc.contracts.NEO.GetValidatorsInternal(bc, cache) +// GetValidators returns next block validators. +func (bc *Blockchain) GetValidators() ([]*keys.PublicKey, error) { + return bc.contracts.NEO.GetNextBlockValidatorsInternal(bc, bc.dao) } -// GetEnrollments returns all registered validators and non-registered SB validators -func (bc *Blockchain) GetEnrollments() ([]*state.Validator, error) { - validators := bc.dao.GetValidators() - standByValidators, err := bc.GetStandByValidators() - if err != nil { - return nil, err - } - uniqueSBValidators := standByValidators.Unique() - - var result []*state.Validator - for _, validator := range validators { - if validator.Registered { - result = append(result, validator) - } - } - for _, sBValidator := range uniqueSBValidators { - isAdded := false - for _, v := range result { - if v.PublicKey == sBValidator { - isAdded = true - break - } - } - if !isAdded { - result = append(result, &state.Validator{ - PublicKey: sBValidator, - Registered: false, - Votes: 0, - }) - } - } - return result, nil -} - -func (bc *Blockchain) processStateTX(dao *dao.Cached, t *transaction.Transaction, tx *transaction.StateTX) error { - for _, desc := range tx.Descriptors { - switch desc.Type { - case transaction.Account: - if err := bc.processAccountStateDescriptor(desc, t, dao); err != nil { - return err - } - case transaction.Validator: - if err := processValidatorStateDescriptor(desc, dao); err != nil { - return err - } - } - } - return nil -} - -func processEnrollmentTX(dao *dao.Cached, tx *transaction.EnrollmentTX) error { - validatorState, err := dao.GetValidatorStateOrNew(&tx.PublicKey) - if err != nil { - return err - } - validatorState.Registered = true - return dao.PutValidatorState(validatorState) +// GetEnrollments returns all registered validators. +func (bc *Blockchain) GetEnrollments() ([]state.Validator, error) { + return bc.contracts.NEO.GetRegisteredValidators(bc.dao) } // GetScriptHashesForVerifying returns all the ScriptHashes of a transaction which will be use diff --git a/pkg/core/blockchainer/blockchainer.go b/pkg/core/blockchainer/blockchainer.go index 0393a4059..d749ec995 100644 --- a/pkg/core/blockchainer/blockchainer.go +++ b/pkg/core/blockchainer/blockchainer.go @@ -24,7 +24,7 @@ type Blockchainer interface { HeaderHeight() uint32 GetBlock(hash util.Uint256) (*block.Block, error) GetContractState(hash util.Uint160) *state.Contract - GetEnrollments() ([]*state.Validator, error) + GetEnrollments() ([]state.Validator, error) GetHeaderHash(int) util.Uint256 GetHeader(hash util.Uint256) (*block.Header, error) CurrentHeaderHash() util.Uint256 @@ -36,7 +36,7 @@ type Blockchainer interface { GetAppExecResult(util.Uint256) (*state.AppExecResult, error) GetNEP5TransferLog(util.Uint160) *state.NEP5TransferLog GetNEP5Balances(util.Uint160) *state.NEP5Balances - GetValidators(txes ...*transaction.Transaction) ([]*keys.PublicKey, error) + GetValidators() ([]*keys.PublicKey, error) GetStandByValidators() (keys.PublicKeys, error) GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error) GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem diff --git a/pkg/core/dao/dao.go b/pkg/core/dao/dao.go index 23fab4fc8..f803078a4 100644 --- a/pkg/core/dao/dao.go +++ b/pkg/core/dao/dao.go @@ -10,7 +10,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/core/transaction" - "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/util" ) @@ -20,7 +19,6 @@ type DAO interface { AppendNEP5Transfer(acc util.Uint160, index uint32, tr *state.NEP5Transfer) (bool, error) DeleteContractState(hash util.Uint160) error DeleteStorageItem(scripthash util.Uint160, key []byte) error - DeleteValidatorState(vs *state.Validator) error GetAccountState(hash util.Uint160) (*state.Account, error) GetAccountStateOrNew(hash util.Uint160) (*state.Account, error) GetAndDecode(entity io.Serializable, key []byte) error @@ -39,9 +37,6 @@ type DAO interface { GetStorageItemsWithPrefix(hash util.Uint160, prefix []byte) (map[string]*state.StorageItem, error) GetTransaction(hash util.Uint256) (*transaction.Transaction, uint32, error) GetUnspentCoinState(hash util.Uint256) (*state.UnspentCoin, error) - GetValidatorState(publicKey *keys.PublicKey) (*state.Validator, error) - GetValidatorStateOrNew(publicKey *keys.PublicKey) (*state.Validator, error) - GetValidators() []*state.Validator GetVersion() (string, error) GetWrapped() DAO HasTransaction(hash util.Uint256) bool @@ -57,7 +52,6 @@ type DAO interface { PutNEP5TransferLog(acc util.Uint160, index uint32, lg *state.NEP5TransferLog) error PutStorageItem(scripthash util.Uint160, key []byte, si *state.StorageItem) error PutUnspentCoinState(hash util.Uint256, ucs *state.UnspentCoin) error - PutValidatorState(vs *state.Validator) error PutVersion(v string) error StoreAsBlock(block *block.Block, sysFee uint32) error StoreAsCurrentBlock(block *block.Block) error @@ -307,61 +301,6 @@ func (dao *Simple) putUnspentCoinState(hash util.Uint256, ucs *state.UnspentCoin // -- end unspent coins. -// -- start validator. - -// GetValidatorStateOrNew gets validator from store or created new one in case of error. -func (dao *Simple) GetValidatorStateOrNew(publicKey *keys.PublicKey) (*state.Validator, error) { - validatorState, err := dao.GetValidatorState(publicKey) - if err != nil { - if err != storage.ErrKeyNotFound { - return nil, err - } - validatorState = &state.Validator{PublicKey: publicKey} - } - return validatorState, nil - -} - -// GetValidators returns all validators from store. -func (dao *Simple) GetValidators() []*state.Validator { - var validators []*state.Validator - dao.Store.Seek(storage.STValidator.Bytes(), func(k, v []byte) { - r := io.NewBinReaderFromBuf(v) - validator := &state.Validator{} - validator.DecodeBinary(r) - if r.Err != nil { - return - } - validators = append(validators, validator) - }) - return validators -} - -// GetValidatorState returns validator by publicKey. -func (dao *Simple) GetValidatorState(publicKey *keys.PublicKey) (*state.Validator, error) { - validatorState := &state.Validator{} - key := storage.AppendPrefix(storage.STValidator, publicKey.Bytes()) - err := dao.GetAndDecode(validatorState, key) - if err != nil { - return nil, err - } - return validatorState, nil -} - -// PutValidatorState puts given Validator into the given store. -func (dao *Simple) PutValidatorState(vs *state.Validator) error { - key := storage.AppendPrefix(storage.STValidator, vs.PublicKey.Bytes()) - return dao.Put(vs, key) -} - -// DeleteValidatorState deletes given Validator into the given store. -func (dao *Simple) DeleteValidatorState(vs *state.Validator) error { - key := storage.AppendPrefix(storage.STValidator, vs.PublicKey.Bytes()) - return dao.Store.Delete(key) -} - -// -- end validator. - // -- start notification event. // GetAppExecResult gets application execution result from the diff --git a/pkg/core/dao/dao_test.go b/pkg/core/dao/dao_test.go index 24ec938f5..65e74b50e 100644 --- a/pkg/core/dao/dao_test.go +++ b/pkg/core/dao/dao_test.go @@ -113,61 +113,6 @@ func TestPutGetUnspentCoinState(t *testing.T) { require.Equal(t, unspentCoinState, gotUnspentCoinState) } -func TestGetValidatorStateOrNew_New(t *testing.T) { - dao := NewSimple(storage.NewMemoryStore()) - publicKey := &keys.PublicKey{} - validatorState, err := dao.GetValidatorStateOrNew(publicKey) - require.NoError(t, err) - require.NotNil(t, validatorState) -} - -func TestPutGetValidatorState(t *testing.T) { - dao := NewSimple(storage.NewMemoryStore()) - publicKey := &keys.PublicKey{} - validatorState := &state.Validator{ - PublicKey: publicKey, - Registered: false, - Votes: 0, - } - err := dao.PutValidatorState(validatorState) - require.NoError(t, err) - gotValidatorState, err := dao.GetValidatorState(publicKey) - require.NoError(t, err) - require.Equal(t, validatorState, gotValidatorState) -} - -func TestDeleteValidatorState(t *testing.T) { - dao := NewSimple(storage.NewMemoryStore()) - publicKey := &keys.PublicKey{} - validatorState := &state.Validator{ - PublicKey: publicKey, - Registered: false, - Votes: 0, - } - err := dao.PutValidatorState(validatorState) - require.NoError(t, err) - err = dao.DeleteValidatorState(validatorState) - require.NoError(t, err) - gotValidatorState, err := dao.GetValidatorState(publicKey) - require.Error(t, err) - require.Nil(t, gotValidatorState) -} - -func TestGetValidators(t *testing.T) { - dao := NewSimple(storage.NewMemoryStore()) - publicKey := &keys.PublicKey{} - validatorState := &state.Validator{ - PublicKey: publicKey, - Registered: false, - Votes: 0, - } - err := dao.PutValidatorState(validatorState) - require.NoError(t, err) - validators := dao.GetValidators() - require.Equal(t, validatorState, validators[0]) - require.Len(t, validators, 1) -} - func TestPutGetAppExecResult(t *testing.T) { dao := NewSimple(storage.NewMemoryStore()) hash := random.Uint256() diff --git a/pkg/core/interop_neo.go b/pkg/core/interop_neo.go index d144891e1..065ecd8e5 100644 --- a/pkg/core/interop_neo.go +++ b/pkg/core/interop_neo.go @@ -236,13 +236,6 @@ func witnessGetVerificationScript(ic *interop.Context, v *vm.VM) error { return nil } -// bcGetValidators returns validators. -func bcGetValidators(ic *interop.Context, v *vm.VM) error { - validators := ic.DAO.GetValidators() - v.Estack().PushVal(validators) - return nil -} - // popInputFromVM returns transaction.Input from the first estack element. func popInputFromVM(v *vm.VM) (*transaction.Input, error) { inInterface := v.Estack().Pop().Value() diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 7a4d276c8..0f2c2c203 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -139,7 +139,6 @@ var neoInterops = []interop.Function{ {Name: "Neo.Blockchain.GetHeight", Func: bcGetHeight, Price: 1}, {Name: "Neo.Blockchain.GetTransaction", Func: bcGetTransaction, Price: 100}, {Name: "Neo.Blockchain.GetTransactionHeight", Func: bcGetTransactionHeight, Price: 100}, - {Name: "Neo.Blockchain.GetValidators", Func: bcGetValidators, Price: 200}, {Name: "Neo.Contract.Create", Func: contractCreate, Price: 0}, {Name: "Neo.Contract.Destroy", Func: contractDestroy, Price: 1}, {Name: "Neo.Contract.GetScript", Func: contractGetScript, Price: 1}, @@ -225,7 +224,6 @@ var neoInterops = []interop.Function{ {Name: "AntShares.Blockchain.GetHeader", Func: bcGetHeader, Price: 100}, {Name: "AntShares.Blockchain.GetHeight", Func: bcGetHeight, Price: 1}, {Name: "AntShares.Blockchain.GetTransaction", Func: bcGetTransaction, Price: 100}, - {Name: "AntShares.Blockchain.GetValidators", Func: bcGetValidators, Price: 200}, {Name: "AntShares.Contract.Create", Func: contractCreate, Price: 0}, {Name: "AntShares.Contract.Destroy", Func: contractDestroy, Price: 1}, {Name: "AntShares.Contract.GetScript", Func: contractGetScript, Price: 1}, diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 41c5a5c6c..f159b18ad 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -31,12 +31,6 @@ type keyWithVotes struct { Votes *big.Int } -// pkeyWithVotes is a deserialized key with votes balance. -type pkeyWithVotes struct { - Key *keys.PublicKey - Votes *big.Int -} - const ( neoSyscallName = "Neo.Native.Tokens.NEO" // NEOTotalSupply is the total amount of NEO in the system. @@ -349,6 +343,24 @@ func (n *NEO) getRegisteredValidators(d dao.DAO) ([]keyWithVotes, error) { return arr, nil } +// GetRegisteredValidators returns current registered validators list with keys +// and votes. +func (n *NEO) GetRegisteredValidators(d dao.DAO) ([]state.Validator, error) { + kvs, err := n.getRegisteredValidators(d) + if err != nil { + return nil, err + } + arr := make([]state.Validator, len(kvs)) + for i := range kvs { + arr[i].Key, err = keys.NewPublicKeyFromBytes([]byte(kvs[i].Key)) + if err != nil { + return nil, err + } + arr[i].Votes = kvs[i].Votes + } + return arr, nil +} + func (n *NEO) getRegisteredValidatorsCall(ic *interop.Context, _ []vm.StackItem) vm.StackItem { validators, err := n.getRegisteredValidators(ic.DAO) if err != nil { @@ -374,18 +386,10 @@ func (n *NEO) GetValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (ke if err != nil { return nil, err } - validatorsBytes, err := n.getRegisteredValidators(d) + validators, err := n.GetRegisteredValidators(d) if err != nil { return nil, err } - validators := make([]pkeyWithVotes, len(validatorsBytes)) - for i := range validatorsBytes { - validators[i].Key, err = keys.NewPublicKeyFromBytes([]byte(validatorsBytes[i].Key)) - if err != nil { - return nil, err - } - validators[i].Votes = validatorsBytes[i].Votes - } sort.Slice(validators, func(i, j int) bool { // The most-voted validators should end up in the front of the list. cmp := validators[i].Votes.Cmp(validators[j].Votes) @@ -446,7 +450,7 @@ func (n *NEO) getNextBlockValidators(ic *interop.Context, _ []vm.StackItem) vm.S func (n *NEO) GetNextBlockValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) { si := d.GetStorageItem(n.Hash, nextValidatorsKey) if si == nil { - return bc.GetStandByValidators() + return n.GetValidatorsInternal(bc, d) } pubs := keys.PublicKeys{} err := pubs.DecodeBytes(si.Value) diff --git a/pkg/core/state/validator.go b/pkg/core/state/validator.go index ecdc8cc67..bdfba6ed1 100644 --- a/pkg/core/state/validator.go +++ b/pkg/core/state/validator.go @@ -1,39 +1,13 @@ package state import ( + "math/big" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neo-go/pkg/io" - "github.com/nspcc-dev/neo-go/pkg/util" ) -// Validator holds the state of a validator. +// Validator holds the state of a validator (its key and votes balance). type Validator struct { - PublicKey *keys.PublicKey - Registered bool - Votes util.Fixed8 -} - -// RegisteredAndHasVotes returns true or false whether Validator is registered and has votes. -func (vs *Validator) RegisteredAndHasVotes() bool { - return vs.Registered && vs.Votes > util.Fixed8(0) -} - -// UnregisteredAndHasNoVotes returns true when Validator is not registered and has no votes. -func (vs *Validator) UnregisteredAndHasNoVotes() bool { - return !vs.Registered && vs.Votes == 0 -} - -// EncodeBinary encodes Validator to the given BinWriter. -func (vs *Validator) EncodeBinary(bw *io.BinWriter) { - vs.PublicKey.EncodeBinary(bw) - bw.WriteBool(vs.Registered) - vs.Votes.EncodeBinary(bw) -} - -// DecodeBinary decodes Validator from the given BinReader. -func (vs *Validator) DecodeBinary(reader *io.BinReader) { - vs.PublicKey = &keys.PublicKey{} - vs.PublicKey.DecodeBinary(reader) - vs.Registered = reader.ReadBool() - vs.Votes.DecodeBinary(reader) + Key *keys.PublicKey + Votes *big.Int } diff --git a/pkg/core/state/validator_test.go b/pkg/core/state/validator_test.go deleted file mode 100644 index 1136aa189..000000000 --- a/pkg/core/state/validator_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package state - -import ( - "math/big" - "testing" - - "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neo-go/pkg/internal/testserdes" - "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/stretchr/testify/require" -) - -func TestValidatorState_DecodeEncodeBinary(t *testing.T) { - state := &Validator{ - PublicKey: &keys.PublicKey{}, - Registered: false, - Votes: util.Fixed8(10), - } - - testserdes.EncodeDecodeBinary(t, state, new(Validator)) -} - -func TestRegisteredAndHasVotes_Registered(t *testing.T) { - state := &Validator{ - PublicKey: &keys.PublicKey{ - X: big.NewInt(1), - Y: big.NewInt(1), - }, - Registered: true, - Votes: 0, - } - require.False(t, state.RegisteredAndHasVotes()) -} - -func TestRegisteredAndHasVotes_RegisteredWithVotes(t *testing.T) { - state := &Validator{ - PublicKey: &keys.PublicKey{ - X: big.NewInt(1), - Y: big.NewInt(1), - }, - Registered: true, - Votes: 1, - } - require.True(t, state.RegisteredAndHasVotes()) -} - -func TestRegisteredAndHasVotes_NotRegisteredWithVotes(t *testing.T) { - state := &Validator{ - PublicKey: &keys.PublicKey{ - X: big.NewInt(1), - Y: big.NewInt(1), - }, - Registered: false, - Votes: 1, - } - require.False(t, state.RegisteredAndHasVotes()) -} diff --git a/pkg/network/helper_test.go b/pkg/network/helper_test.go index 48db395a7..6ef6487aa 100644 --- a/pkg/network/helper_test.go +++ b/pkg/network/helper_test.go @@ -96,13 +96,13 @@ func (chain testChain) GetNEP5TransferLog(util.Uint160) *state.NEP5TransferLog { func (chain testChain) GetNEP5Balances(util.Uint160) *state.NEP5Balances { panic("TODO") } -func (chain testChain) GetValidators(...*transaction.Transaction) ([]*keys.PublicKey, error) { +func (chain testChain) GetValidators() ([]*keys.PublicKey, error) { panic("TODO") } func (chain testChain) GetStandByValidators() (keys.PublicKeys, error) { panic("TODO") } -func (chain testChain) GetEnrollments() ([]*state.Validator, error) { +func (chain testChain) GetEnrollments() ([]state.Validator, error) { panic("TODO") } func (chain testChain) GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error) { diff --git a/pkg/rpc/response/result/validator.go b/pkg/rpc/response/result/validator.go index 19c5fefda..37862511d 100644 --- a/pkg/rpc/response/result/validator.go +++ b/pkg/rpc/response/result/validator.go @@ -2,13 +2,12 @@ package result import ( "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neo-go/pkg/util" ) // Validator used for the representation of // state.Validator on the RPC Server. type Validator struct { PublicKey keys.PublicKey `json:"publickey"` - Votes util.Fixed8 `json:"votes"` + Votes int64 `json:"votes,string"` Active bool `json:"active"` } diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index 38a080746..b4164ef2f 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -815,9 +815,9 @@ func (s *Server) getValidators(_ request.Params) (interface{}, error) { var res []result.Validator for _, v := range enrollments { res = append(res, result.Validator{ - PublicKey: *v.PublicKey, - Votes: v.Votes, - Active: validators.Contains(v.PublicKey), + PublicKey: *v.Key, + Votes: v.Votes.Int64(), + Active: validators.Contains(v.Key), }) } return res, nil From e6f5cffff64b560c4d84df91e02f98463b1c5472 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Sun, 26 Apr 2020 20:16:39 +0300 Subject: [PATCH 13/13] transaction: drop Enrollment and State types They're completely replaced now by the NEO native contract voting system. --- config/protocol.mainnet.yml | 1 - config/protocol.privnet.docker.four.yml | 1 - config/protocol.privnet.docker.one.yml | 1 - config/protocol.privnet.docker.single.yml | 1 - config/protocol.privnet.docker.three.yml | 1 - config/protocol.privnet.docker.two.yml | 1 - config/protocol.testnet.yml | 1 - config/protocol.unit_testnet.yml | 1 - pkg/config/protocol_config.go | 7 +- pkg/core/blockchain.go | 102 ---------------------- pkg/core/transaction/enrollment.go | 43 --------- pkg/core/transaction/enrollment_test.go | 17 ---- pkg/core/transaction/state.go | 37 -------- pkg/core/transaction/state_descriptor.go | 81 ----------------- pkg/core/transaction/state_test.go | 31 ------- pkg/core/transaction/transaction.go | 29 +----- pkg/core/transaction/transaction_test.go | 56 ------------ pkg/core/transaction/type.go | 10 --- 18 files changed, 6 insertions(+), 415 deletions(-) delete mode 100644 pkg/core/transaction/enrollment.go delete mode 100644 pkg/core/transaction/enrollment_test.go delete mode 100644 pkg/core/transaction/state.go delete mode 100644 pkg/core/transaction/state_descriptor.go delete mode 100644 pkg/core/transaction/state_test.go diff --git a/config/protocol.mainnet.yml b/config/protocol.mainnet.yml index a2a10721b..302717b7f 100644 --- a/config/protocol.mainnet.yml +++ b/config/protocol.mainnet.yml @@ -24,7 +24,6 @@ ProtocolConfiguration: - seed9.ngd.network:10333 - seed10.ngd.network:10333 SystemFee: - EnrollmentTransaction: 1000 IssueTransaction: 500 RegisterTransaction: 10000 VerifyBlocks: true diff --git a/config/protocol.privnet.docker.four.yml b/config/protocol.privnet.docker.four.yml index 9addb3070..e9bc5e8e1 100644 --- a/config/protocol.privnet.docker.four.yml +++ b/config/protocol.privnet.docker.four.yml @@ -15,7 +15,6 @@ ProtocolConfiguration: - 172.200.0.3:20335 - 172.200.0.4:20336 SystemFee: - EnrollmentTransaction: 1000 IssueTransaction: 500 RegisterTransaction: 10000 VerifyBlocks: true diff --git a/config/protocol.privnet.docker.one.yml b/config/protocol.privnet.docker.one.yml index 365922969..06bbad06e 100644 --- a/config/protocol.privnet.docker.one.yml +++ b/config/protocol.privnet.docker.one.yml @@ -15,7 +15,6 @@ ProtocolConfiguration: - 172.200.0.3:20335 - 172.200.0.4:20336 SystemFee: - EnrollmentTransaction: 1000 IssueTransaction: 500 RegisterTransaction: 10000 VerifyBlocks: true diff --git a/config/protocol.privnet.docker.single.yml b/config/protocol.privnet.docker.single.yml index 015052fb4..5fdb643bc 100644 --- a/config/protocol.privnet.docker.single.yml +++ b/config/protocol.privnet.docker.single.yml @@ -9,7 +9,6 @@ ProtocolConfiguration: SeedList: - 172.200.0.1:20333 SystemFee: - EnrollmentTransaction: 1000 IssueTransaction: 500 RegisterTransaction: 10000 VerifyBlocks: true diff --git a/config/protocol.privnet.docker.three.yml b/config/protocol.privnet.docker.three.yml index 89d5e59c5..a59daccbe 100644 --- a/config/protocol.privnet.docker.three.yml +++ b/config/protocol.privnet.docker.three.yml @@ -15,7 +15,6 @@ ProtocolConfiguration: - 172.200.0.3:20335 - 172.200.0.4:20336 SystemFee: - EnrollmentTransaction: 1000 IssueTransaction: 500 RegisterTransaction: 10000 VerifyBlocks: true diff --git a/config/protocol.privnet.docker.two.yml b/config/protocol.privnet.docker.two.yml index 6b4fda670..082ba83b2 100644 --- a/config/protocol.privnet.docker.two.yml +++ b/config/protocol.privnet.docker.two.yml @@ -15,7 +15,6 @@ ProtocolConfiguration: - 172.200.0.3:20335 - 172.200.0.4:20336 SystemFee: - EnrollmentTransaction: 1000 IssueTransaction: 500 RegisterTransaction: 10000 VerifyBlocks: true diff --git a/config/protocol.testnet.yml b/config/protocol.testnet.yml index c36540d57..4a483bfcc 100644 --- a/config/protocol.testnet.yml +++ b/config/protocol.testnet.yml @@ -24,7 +24,6 @@ ProtocolConfiguration: - seed9.ngd.network:20333 - seed10.ngd.network:20333 SystemFee: - EnrollmentTransaction: 10 IssueTransaction: 5 RegisterTransaction: 100 VerifyBlocks: true diff --git a/config/protocol.unit_testnet.yml b/config/protocol.unit_testnet.yml index d445c7cb9..2c809c523 100644 --- a/config/protocol.unit_testnet.yml +++ b/config/protocol.unit_testnet.yml @@ -14,7 +14,6 @@ ProtocolConfiguration: - 127.0.0.1:20335 - 127.0.0.1:20336 SystemFee: - EnrollmentTransaction: 1000 IssueTransaction: 500 RegisterTransaction: 10000 VerifyBlocks: true diff --git a/pkg/config/protocol_config.go b/pkg/config/protocol_config.go index 405f62b1d..421e3ff9b 100644 --- a/pkg/config/protocol_config.go +++ b/pkg/config/protocol_config.go @@ -47,9 +47,8 @@ type ( // SystemFee fees related to system. SystemFee struct { - EnrollmentTransaction int64 `yaml:"EnrollmentTransaction"` - IssueTransaction int64 `yaml:"IssueTransaction"` - RegisterTransaction int64 `yaml:"RegisterTransaction"` + IssueTransaction int64 `yaml:"IssueTransaction"` + RegisterTransaction int64 `yaml:"RegisterTransaction"` } // NetMode describes the mode the blockchain will operate on. @@ -75,8 +74,6 @@ func (n NetMode) String() string { // TryGetValue returns the system fee base on transaction type. func (s SystemFee) TryGetValue(txType transaction.TXType) util.Fixed8 { switch txType { - case transaction.EnrollmentType: - return util.Fixed8FromInt64(s.EnrollmentTransaction) case transaction.IssueType: return util.Fixed8FromInt64(s.IssueTransaction) case transaction.RegisterType: diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 37ae27923..af99c5de1 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -831,23 +831,6 @@ func processOutputs(tx *transaction.Transaction, dao *dao.Cached) error { return nil } -func (bc *Blockchain) processAccountStateDescriptor(descriptor *transaction.StateDescriptor, t *transaction.Transaction, dao *dao.Cached) error { - hash, err := util.Uint160DecodeBytesBE(descriptor.Key) - if err != nil { - return err - } - - if descriptor.Field == "Votes" { - votes := keys.PublicKeys{} - if err := votes.DecodeBytes(descriptor.Value); err != nil { - return err - } - ic := bc.newInteropContext(trigger.Application, dao, nil, t) - return bc.contracts.NEO.VoteInternal(ic, hash, votes) - } - return nil -} - // persist flushes current in-memory Store contents to the persistent storage. func (bc *Blockchain) persist() error { var ( @@ -1268,63 +1251,6 @@ func (bc *Blockchain) verifyTx(t *transaction.Transaction, block *block.Block) e if inv.Gas.FractionalValue() != 0 { return errors.New("invocation gas can only be integer") } - case transaction.StateType: - stx := t.Data.(*transaction.StateTX) - for _, desc := range stx.Descriptors { - switch desc.Type { - case transaction.Account: - if desc.Field != "Votes" { - return errors.New("bad field in account descriptor") - } - votes := keys.PublicKeys{} - err := votes.DecodeBytes(desc.Value) - if err != nil { - return err - } - if len(votes) > native.MaxValidatorsVoted { - return errors.New("voting candidate limit exceeded") - } - hash, err := util.Uint160DecodeBytesBE(desc.Key) - if err != nil { - return err - } - account, err := bc.dao.GetAccountStateOrNew(hash) - if err != nil { - return err - } - if account.IsFrozen { - return errors.New("account is frozen") - } - if votes.Len() > 0 { - balance := account.GetBalanceValues()[GoverningTokenID()] - if balance == 0 { - return errors.New("no governing tokens available to vote") - } - validators, err := bc.GetEnrollments() - if err != nil { - return err - } - for _, k := range votes { - var isRegistered bool - for i := range validators { - if k.Equal(validators[i].Key) { - isRegistered = true - break - } - } - if !isRegistered { - return errors.New("vote for unregistered validator") - } - } - } - case transaction.Validator: - if desc.Field != "Registered" { - return errors.New("bad field in validator descriptor") - } - default: - return errors.New("bad descriptor type") - } - } } return bc.verifyTxWitnesses(t, block) @@ -1662,9 +1588,6 @@ func (bc *Blockchain) GetScriptHashesForVerifying(t *transaction.Transaction) ([ for i := range refs { hashes[refs[i].Out.ScriptHash] = true } - case transaction.EnrollmentType: - etx := t.Data.(*transaction.EnrollmentTX) - hashes[etx.PublicKey.GetScriptHash()] = true case transaction.IssueType: for _, res := range refsAndOutsToResults(references, t.Outputs) { if res.Amount < 0 { @@ -1678,31 +1601,6 @@ func (bc *Blockchain) GetScriptHashesForVerifying(t *transaction.Transaction) ([ case transaction.RegisterType: reg := t.Data.(*transaction.RegisterTX) hashes[reg.Owner.GetScriptHash()] = true - case transaction.StateType: - stx := t.Data.(*transaction.StateTX) - for _, desc := range stx.Descriptors { - switch desc.Type { - case transaction.Account: - if desc.Field != "Votes" { - return nil, errors.New("bad account state descriptor") - } - hash, err := util.Uint160DecodeBytesBE(desc.Key) - if err != nil { - return nil, err - } - hashes[hash] = true - case transaction.Validator: - if desc.Field != "Registered" { - return nil, errors.New("bad validator state descriptor") - } - key := &keys.PublicKey{} - err := key.DecodeBytes(desc.Key) - if err != nil { - return nil, err - } - hashes[key.GetScriptHash()] = true - } - } } // convert hashes to []util.Uint160 hashesResult := make([]util.Uint160, 0, len(hashes)) diff --git a/pkg/core/transaction/enrollment.go b/pkg/core/transaction/enrollment.go deleted file mode 100644 index 8699ef9d2..000000000 --- a/pkg/core/transaction/enrollment.go +++ /dev/null @@ -1,43 +0,0 @@ -package transaction - -import ( - "math/rand" - - "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neo-go/pkg/io" -) - -// EnrollmentTX transaction represents an enrollment form, which indicates -// that the sponsor of the transaction would like to sign up as a validator. -// The way to sign up is: To construct an EnrollmentTransaction type of transaction, -// and send a deposit to the address of the PublicKey. -// The way to cancel the registration is: Spend the deposit on the address of the PublicKey. -type EnrollmentTX struct { - // PublicKey of the validator. - PublicKey keys.PublicKey -} - -// NewEnrollmentTX creates Transaction of EnrollmentType type. -func NewEnrollmentTX(enrollment *EnrollmentTX) *Transaction { - return &Transaction{ - Type: EnrollmentType, - Version: 0, - Nonce: rand.Uint32(), - Data: enrollment, - Attributes: []Attribute{}, - Inputs: []Input{}, - Outputs: []Output{}, - Scripts: []Witness{}, - Trimmed: false, - } -} - -// DecodeBinary implements Serializable interface. -func (tx *EnrollmentTX) DecodeBinary(r *io.BinReader) { - tx.PublicKey.DecodeBinary(r) -} - -// EncodeBinary implements Serializable interface. -func (tx *EnrollmentTX) EncodeBinary(w *io.BinWriter) { - tx.PublicKey.EncodeBinary(w) -} diff --git a/pkg/core/transaction/enrollment_test.go b/pkg/core/transaction/enrollment_test.go deleted file mode 100644 index 21cbeeb98..000000000 --- a/pkg/core/transaction/enrollment_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package transaction - -//TODO NEO3.0: Update bynary -/* -func TestEncodeDecodeEnrollment(t *testing.T) { - rawtx := "200002ff8ac54687f36bbc31a91b730cc385da8af0b581f2d59d82b5cfef824fd271f60001d3d3b7028d61fea3b7803fda3d7f0a1f7262d38e5e1c8987b0313e0a94574151000001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c60005441d11600000050ac4949596f5b62fef7be4d1c3e494e6048ed4a01414079d78189d591097b17657a62240c93595e8233dc81157ea2cd477813f09a11fd72845e6bd97c5a3dda125985ea3d5feca387e9933649a9a671a69ab3f6301df6232102ff8ac54687f36bbc31a91b730cc385da8af0b581f2d59d82b5cfef824fd271f6ac" - tx := decodeTransaction(rawtx, t) - assert.Equal(t, "988832f693785dcbcb8d5a0e9d5d22002adcbfb1eb6bbeebf8c494fff580e147", tx.Hash().StringLE()) - assert.Equal(t, EnrollmentType, tx.Type) - assert.IsType(t, tx.Data, &EnrollmentTX{}) - assert.Equal(t, 0, int(tx.Version)) - - data, err := testserdes.EncodeBinary(tx) - assert.Equal(t, nil, err) - assert.Equal(t, rawtx, hex.EncodeToString(data)) -} -*/ diff --git a/pkg/core/transaction/state.go b/pkg/core/transaction/state.go deleted file mode 100644 index 84b205cf8..000000000 --- a/pkg/core/transaction/state.go +++ /dev/null @@ -1,37 +0,0 @@ -package transaction - -import ( - "math/rand" - - "github.com/nspcc-dev/neo-go/pkg/io" -) - -// StateTX represents a state transaction. -type StateTX struct { - Descriptors []*StateDescriptor -} - -// NewStateTX creates Transaction of StateType type. -func NewStateTX(state *StateTX) *Transaction { - return &Transaction{ - Type: StateType, - Version: 0, - Nonce: rand.Uint32(), - Data: state, - Attributes: []Attribute{}, - Inputs: []Input{}, - Outputs: []Output{}, - Scripts: []Witness{}, - Trimmed: false, - } -} - -// DecodeBinary implements Serializable interface. -func (tx *StateTX) DecodeBinary(r *io.BinReader) { - r.ReadArray(&tx.Descriptors) -} - -// EncodeBinary implements Serializable interface. -func (tx *StateTX) EncodeBinary(w *io.BinWriter) { - w.WriteArray(tx.Descriptors) -} diff --git a/pkg/core/transaction/state_descriptor.go b/pkg/core/transaction/state_descriptor.go deleted file mode 100644 index 81d4295fe..000000000 --- a/pkg/core/transaction/state_descriptor.go +++ /dev/null @@ -1,81 +0,0 @@ -package transaction - -import ( - "encoding/hex" - "encoding/json" - - "github.com/nspcc-dev/neo-go/pkg/io" -) - -// DescStateType represents the type of StateDescriptor. -type DescStateType uint8 - -// Valid DescStateType constants. -const ( - Account DescStateType = 0x40 - Validator DescStateType = 0x48 -) - -// StateDescriptor .. -type StateDescriptor struct { - Type DescStateType - Key []byte - Value []byte - Field string -} - -// DecodeBinary implements Serializable interface. -func (s *StateDescriptor) DecodeBinary(r *io.BinReader) { - s.Type = DescStateType(r.ReadB()) - - s.Key = r.ReadVarBytes() - s.Field = r.ReadString() - s.Value = r.ReadVarBytes() -} - -// EncodeBinary implements Serializable interface. -func (s *StateDescriptor) EncodeBinary(w *io.BinWriter) { - w.WriteB(byte(s.Type)) - w.WriteVarBytes(s.Key) - w.WriteString(s.Field) - w.WriteVarBytes(s.Value) -} - -// stateDescriptor is a wrapper for StateDescriptor -type stateDescriptor struct { - Type DescStateType `json:"type"` - Key string `json:"key"` - Value string `json:"value"` - Field string `json:"field"` -} - -// MarshalJSON implements json.Marshaler interface. -func (s *StateDescriptor) MarshalJSON() ([]byte, error) { - return json.Marshal(&stateDescriptor{ - Type: s.Type, - Key: hex.EncodeToString(s.Key), - Value: hex.EncodeToString(s.Value), - Field: s.Field, - }) -} - -// UnmarshalJSON implements json.Unmarshaler interface. -func (s *StateDescriptor) UnmarshalJSON(data []byte) error { - t := new(stateDescriptor) - if err := json.Unmarshal(data, t); err != nil { - return err - } - key, err := hex.DecodeString(t.Key) - if err != nil { - return err - } - value, err := hex.DecodeString(t.Value) - if err != nil { - return err - } - s.Key = key - s.Value = value - s.Field = t.Field - s.Type = t.Type - return nil -} diff --git a/pkg/core/transaction/state_test.go b/pkg/core/transaction/state_test.go deleted file mode 100644 index 87acb1adf..000000000 --- a/pkg/core/transaction/state_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package transaction - -//TODO NEO3.0: Update binary -/* -func TestEncodeDecodeState(t *testing.T) { - // transaction taken from testnet 8abf5ebdb9a8223b12109513647f45bd3c0a6cf1a6346d56684cff71ba308724 - rawtx := "900001482103c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c10a5265676973746572656401010001cb4184f0a96e72656c1fbdd4f75cca567519e909fd43cefcec13d6c6abcb92a1000001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6000b8fb050109000071f9cf7f0ec74ec0b0f28a92b12e1081574c0af00141408780d7b3c0aadc5398153df5e2f1cf159db21b8b0f34d3994d865433f79fafac41683783c48aef510b67660e3157b701b9ca4dd9946a385d578fba7dd26f4849232103c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1ac" - tx := decodeTransaction(rawtx, t) - assert.Equal(t, StateType, tx.Type) - assert.IsType(t, tx.Data, &StateTX{}) - assert.Equal(t, "8abf5ebdb9a8223b12109513647f45bd3c0a6cf1a6346d56684cff71ba308724", tx.Hash().StringLE()) - - assert.Equal(t, 1, len(tx.Inputs)) - input := tx.Inputs[0] - assert.Equal(t, "a192cbabc6d613ecfcce43fd09e9197556ca5cf7d4bd1f6c65726ea9f08441cb", input.PrevHash.StringLE()) - assert.Equal(t, uint16(0), input.PrevIndex) - - s := tx.Data.(*StateTX) - assert.Equal(t, 1, len(s.Descriptors)) - descriptor := s.Descriptors[0] - assert.Equal(t, "03c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1", hex.EncodeToString(descriptor.Key)) - assert.Equal(t, "Registered", descriptor.Field) - assert.Equal(t, []byte{0x01}, descriptor.Value) - assert.Equal(t, Validator, descriptor.Type) - - // Encode - data, err := testserdes.EncodeBinary(tx) - assert.NoError(t, err) - assert.Equal(t, rawtx, hex.EncodeToString(data)) -} -*/ diff --git a/pkg/core/transaction/transaction.go b/pkg/core/transaction/transaction.go index 7a594cce9..c404a5e29 100644 --- a/pkg/core/transaction/transaction.go +++ b/pkg/core/transaction/transaction.go @@ -7,7 +7,6 @@ import ( "fmt" "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/address" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/util" @@ -164,12 +163,6 @@ func (t *Transaction) decodeData(r *io.BinReader) { case IssueType: t.Data = &IssueTX{} t.Data.(*IssueTX).DecodeBinary(r) - case EnrollmentType: - t.Data = &EnrollmentTX{} - t.Data.(*EnrollmentTX).DecodeBinary(r) - case StateType: - t.Data = &StateTX{} - t.Data.(*StateTX).DecodeBinary(r) default: r.Err = fmt.Errorf("invalid TX type %x", t.Type) } @@ -281,12 +274,10 @@ type transactionJSON struct { Outputs []Output `json:"vout"` Scripts []Witness `json:"scripts"` - Claims []Input `json:"claims,omitempty"` - PublicKey *keys.PublicKey `json:"pubkey,omitempty"` - Script string `json:"script,omitempty"` - Gas util.Fixed8 `json:"gas,omitempty"` - Asset *registeredAsset `json:"asset,omitempty"` - Descriptors []*StateDescriptor `json:"descriptors,omitempty"` + Claims []Input `json:"claims,omitempty"` + Script string `json:"script,omitempty"` + Gas util.Fixed8 `json:"gas,omitempty"` + Asset *registeredAsset `json:"asset,omitempty"` } // MarshalJSON implements json.Marshaler interface. @@ -307,8 +298,6 @@ func (t *Transaction) MarshalJSON() ([]byte, error) { switch t.Type { case ClaimType: tx.Claims = t.Data.(*ClaimTX).Claims - case EnrollmentType: - tx.PublicKey = &t.Data.(*EnrollmentTX).PublicKey case InvocationType: tx.Script = hex.EncodeToString(t.Data.(*InvocationTX).Script) tx.Gas = t.Data.(*InvocationTX).Gas @@ -322,8 +311,6 @@ func (t *Transaction) MarshalJSON() ([]byte, error) { Owner: transaction.Owner, Admin: address.Uint160ToString(transaction.Admin), } - case StateType: - tx.Descriptors = t.Data.(*StateTX).Descriptors } return json.Marshal(tx) } @@ -354,10 +341,6 @@ func (t *Transaction) UnmarshalJSON(data []byte) error { t.Data = &ClaimTX{ Claims: tx.Claims, } - case EnrollmentType: - t.Data = &EnrollmentTX{ - PublicKey: *tx.PublicKey, - } case InvocationType: bytes, err := hex.DecodeString(tx.Script) if err != nil { @@ -381,10 +364,6 @@ func (t *Transaction) UnmarshalJSON(data []byte) error { Owner: tx.Asset.Owner, Admin: admin, } - case StateType: - t.Data = &StateTX{ - Descriptors: tx.Descriptors, - } case ContractType: t.Data = &ContractTX{} case IssueType: diff --git a/pkg/core/transaction/transaction_test.go b/pkg/core/transaction/transaction_test.go index 4a47eb6ef..9050026ee 100644 --- a/pkg/core/transaction/transaction_test.go +++ b/pkg/core/transaction/transaction_test.go @@ -154,32 +154,6 @@ func TestMarshalUnmarshalJSONClaimTX(t *testing.T) { testserdes.MarshalUnmarshalJSON(t, tx, new(Transaction)) } -func TestMarshalUnmarshalJSONEnrollmentTX(t *testing.T) { - str := "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c" - pubKey, err := keys.NewPublicKeyFromString(str) - require.NoError(t, err) - tx := &Transaction{ - Type: EnrollmentType, - Version: 5, - Data: &EnrollmentTX{PublicKey: *pubKey}, - Attributes: []Attribute{}, - Inputs: []Input{{ - PrevHash: util.Uint256{5, 6, 7, 8}, - PrevIndex: uint16(12), - }}, - Outputs: []Output{{ - AssetID: util.Uint256{1, 2, 3}, - Amount: util.Fixed8FromInt64(1), - ScriptHash: util.Uint160{1, 2, 3}, - Position: 0, - }}, - Scripts: []Witness{}, - Trimmed: false, - } - - testserdes.MarshalUnmarshalJSON(t, tx, new(Transaction)) -} - func TestMarshalUnmarshalJSONInvocationTX(t *testing.T) { tx := &Transaction{ Type: InvocationType, @@ -236,33 +210,3 @@ func TestMarshalUnmarshalJSONRegisterTX(t *testing.T) { testserdes.MarshalUnmarshalJSON(t, tx, new(Transaction)) } - -func TestMarshalUnmarshalJSONStateTX(t *testing.T) { - tx := &Transaction{ - Type: StateType, - Version: 5, - Data: &StateTX{ - Descriptors: []*StateDescriptor{&StateDescriptor{ - Type: Validator, - Key: []byte{1, 2, 3}, - Value: []byte{4, 5, 6}, - Field: "Field", - }}, - }, - Attributes: []Attribute{}, - Inputs: []Input{{ - PrevHash: util.Uint256{5, 6, 7, 8}, - PrevIndex: uint16(12), - }}, - Outputs: []Output{{ - AssetID: util.Uint256{1, 2, 3}, - Amount: util.Fixed8FromInt64(1), - ScriptHash: util.Uint160{1, 2, 3}, - Position: 0, - }}, - Scripts: []Witness{}, - Trimmed: false, - } - - testserdes.MarshalUnmarshalJSON(t, tx, new(Transaction)) -} diff --git a/pkg/core/transaction/type.go b/pkg/core/transaction/type.go index c9d972177..8c3ab94a8 100644 --- a/pkg/core/transaction/type.go +++ b/pkg/core/transaction/type.go @@ -14,10 +14,8 @@ const ( MinerType TXType = 0x00 IssueType TXType = 0x01 ClaimType TXType = 0x02 - EnrollmentType TXType = 0x20 RegisterType TXType = 0x40 ContractType TXType = 0x80 - StateType TXType = 0x90 InvocationType TXType = 0xd1 ) @@ -30,14 +28,10 @@ func (t TXType) String() string { return "IssueTransaction" case ClaimType: return "ClaimTransaction" - case EnrollmentType: - return "EnrollmentTransaction" case RegisterType: return "RegisterTransaction" case ContractType: return "ContractTransaction" - case StateType: - return "StateTransaction" case InvocationType: return "InvocationTransaction" default: @@ -70,14 +64,10 @@ func TXTypeFromString(jsonString string) (TXType, error) { return IssueType, nil case "ClaimTransaction": return ClaimType, nil - case "EnrollmentTransaction": - return EnrollmentType, nil case "RegisterTransaction": return RegisterType, nil case "ContractTransaction": return ContractType, nil - case "StateTransaction": - return StateType, nil case "InvocationTransaction": return InvocationType, nil default: