From c80ee952a1a8a5a971cf2579d0985ee091cc39a5 Mon Sep 17 00:00:00 2001 From: Vsevolod Brekelov Date: Mon, 11 Nov 2019 18:25:28 +0300 Subject: [PATCH] core/crypto: add validators and interop for validators add processing of validators while block persist; add validator structure with decoding/encoding; add validator get from store; add EnrollmentTX and StateTX processing; add pubkey decode bytes, unique and contains functions; --- pkg/core/blockchain.go | 322 +++++++++++++++++++++++++++++++---- pkg/core/blockchainer.go | 2 + pkg/core/interop_neo.go | 10 ++ pkg/core/interops.go | 4 +- pkg/core/validator_state.go | 93 ++++++++++ pkg/crypto/keys/publickey.go | 32 ++++ pkg/network/helper_test.go | 4 + 7 files changed, 435 insertions(+), 32 deletions(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index dd861c90e..ddaa4c030 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -6,12 +6,14 @@ import ( "math" "math/big" "sort" + "strconv" "sync/atomic" "time" "github.com/CityOfZion/neo-go/config" "github.com/CityOfZion/neo-go/pkg/core/storage" "github.com/CityOfZion/neo-go/pkg/core/transaction" + "github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/smartcontract" "github.com/CityOfZion/neo-go/pkg/util" @@ -336,33 +338,26 @@ func (bc *Blockchain) processHeader(h *Header, batch storage.Batch, headerList * // is happening here, quite allot as you can see :). If things are wired together // and all tests are in place, we can make a more optimized and cleaner implementation. func (bc *Blockchain) storeBlock(block *Block) error { - var ( - tmpStore = storage.NewMemCachedStore(bc.store) - unspentCoins = make(UnspentCoins) - spentCoins = make(SpentCoins) - accounts = make(Accounts) - assets = make(Assets) - contracts = make(Contracts) - ) + bcTemp := bc.NewBlockChainTemp() - if err := storeAsBlock(tmpStore, block, 0); err != nil { + if err := storeAsBlock(bcTemp.tmpStore, block, 0); err != nil { return err } - if err := storeAsCurrentBlock(tmpStore, block); err != nil { + if err := storeAsCurrentBlock(bcTemp.tmpStore, block); err != nil { return err } for _, tx := range block.Transactions { - if err := storeAsTransaction(tmpStore, tx, block.Index); err != nil { + if err := storeAsTransaction(bcTemp.tmpStore, tx, block.Index); err != nil { return err } - unspentCoins[tx.Hash()] = NewUnspentCoinState(len(tx.Outputs)) + bcTemp.unspentCoins[tx.Hash()] = NewUnspentCoinState(len(tx.Outputs)) // Process TX outputs. for index, output := range tx.Outputs { - account, err := accounts.getAndUpdate(bc.store, output.ScriptHash) + account, err := bcTemp.accounts.getAndUpdate(bc.store, output.ScriptHash) if err != nil { return err } @@ -371,6 +366,15 @@ func (bc *Blockchain) storeBlock(block *Block) error { Index: uint16(index), Value: output.Amount, }) + if output.AssetID.Equals(governingTokenTX().Hash()) && len(account.Votes) > 0 { + for _, vote := range account.Votes { + validatorState, err := bcTemp.validators.getAndUpdate(bc.store, vote) + if err != nil { + return err + } + validatorState.Votes += output.Amount + } + } } // Process TX inputs that are grouped by previous hash. @@ -380,14 +384,14 @@ func (bc *Blockchain) storeBlock(block *Block) error { return fmt.Errorf("could not find previous TX: %s", prevHash) } for _, input := range inputs { - unspent, err := unspentCoins.getAndUpdate(bc.store, input.PrevHash) + unspent, err := bcTemp.unspentCoins.getAndUpdate(bc.store, input.PrevHash) if err != nil { return err } unspent.states[input.PrevIndex] = CoinStateSpent prevTXOutput := prevTX.Outputs[input.PrevIndex] - account, err := accounts.getAndUpdate(bc.store, prevTXOutput.ScriptHash) + account, err := bcTemp.accounts.getAndUpdate(bc.store, prevTXOutput.ScriptHash) if err != nil { return err } @@ -395,7 +399,20 @@ func (bc *Blockchain) storeBlock(block *Block) error { if prevTXOutput.AssetID.Equals(governingTokenTX().Hash()) { spentCoin := NewSpentCoinState(input.PrevHash, prevTXHeight) spentCoin.items[input.PrevIndex] = block.Index - spentCoins[input.PrevHash] = spentCoin + bcTemp.spentCoins[input.PrevHash] = spentCoin + + if len(account.Votes) > 0 { + for _, vote := range account.Votes { + validator, err := bcTemp.validators.getAndUpdate(bc.store, vote) + if err != nil { + return err + } + validator.Votes -= prevTXOutput.Amount + if !validator.RegisteredAndHasVotes() { + delete(bcTemp.validators, vote) + } + } + } } balancesLen := len(account.Balances[prevTXOutput.AssetID]) @@ -419,7 +436,7 @@ func (bc *Blockchain) storeBlock(block *Block) error { // Process the underlying type of the TX. switch t := tx.Data.(type) { case *transaction.RegisterTX: - assets[tx.Hash()] = &AssetState{ + bcTemp.assets[tx.Hash()] = &AssetState{ ID: tx.Hash(), AssetType: t.AssetType, Name: t.Name, @@ -434,7 +451,7 @@ func (bc *Blockchain) storeBlock(block *Block) error { if res.Amount < 0 { var asset *AssetState - asset, ok := assets[res.AssetID] + asset, ok := bcTemp.assets[res.AssetID] if !ok { asset = bc.GetAssetState(res.AssetID) } @@ -442,14 +459,14 @@ func (bc *Blockchain) storeBlock(block *Block) error { return fmt.Errorf("issue failed: no asset %s", res.AssetID) } asset.Available -= res.Amount - assets[res.AssetID] = asset + bcTemp.assets[res.AssetID] = asset } } case *transaction.ClaimTX: // Remove claimed NEO from spent coins making it unavalaible for // additional claims. for _, input := range t.Claims { - scs, err := spentCoins.getAndUpdate(bc.store, input.PrevHash) + scs, err := bcTemp.spentCoins.getAndUpdate(bc.store, input.PrevHash) if err != nil { return err } @@ -458,11 +475,30 @@ func (bc *Blockchain) storeBlock(block *Block) error { delete(scs.items, input.PrevIndex) } else { // Uninitialized, new, forget about it. - delete(spentCoins, input.PrevHash) + delete(bcTemp.spentCoins, input.PrevHash) } } case *transaction.EnrollmentTX: + validator, err := bcTemp.validators.getAndUpdate(bc.store, t.PublicKey) + if err != nil { + return err + } + validator.Registered = true case *transaction.StateTX: + for _, descriptor := range t.Descriptors { + switch descriptor.Type { + case transaction.Account: + err := processAccountStateDescriptor(descriptor, bcTemp) + if err != nil { + return err + } + case transaction.Validator: + err := processValidatorStateDescriptor(descriptor, bcTemp) + if err != nil { + return err + } + } + } case *transaction.PublishTX: var properties smartcontract.PropertyState if t.NeedStorage { @@ -479,10 +515,10 @@ func (bc *Blockchain) storeBlock(block *Block) error { Email: t.Email, Description: t.Description, } - contracts[contract.ScriptHash()] = contract + bcTemp.contracts[contract.ScriptHash()] = contract case *transaction.InvocationTX: - systemInterop := newInteropContext(0x10, bc, tmpStore, block, tx) + systemInterop := newInteropContext(0x10, bc, bcTemp.tmpStore, block, tx) v := bc.spawnVMWithInterops(systemInterop) v.SetCheckedHash(tx.VerificationHash().Bytes()) v.LoadScript(t.Script) @@ -531,7 +567,7 @@ func (bc *Blockchain) storeBlock(block *Block) error { Stack: v.Stack("estack"), Events: systemInterop.notifications, } - err = putAppExecResultIntoStore(tmpStore, aer) + err = putAppExecResultIntoStore(bcTemp.tmpStore, aer) if err != nil { return errors.Wrap(err, "failed to store notifications") } @@ -539,22 +575,25 @@ func (bc *Blockchain) storeBlock(block *Block) error { } // Persist all to storage. - if err := accounts.commit(tmpStore); err != nil { + if err := bcTemp.accounts.commit(bcTemp.tmpStore); err != nil { return err } - if err := unspentCoins.commit(tmpStore); err != nil { + if err := bcTemp.unspentCoins.commit(bcTemp.tmpStore); err != nil { return err } - if err := spentCoins.commit(tmpStore); err != nil { + if err := bcTemp.spentCoins.commit(bcTemp.tmpStore); err != nil { return err } - if err := assets.commit(tmpStore); err != nil { + if err := bcTemp.assets.commit(bcTemp.tmpStore); err != nil { return err } - if err := contracts.commit(tmpStore); err != nil { + if err := bcTemp.contracts.commit(bcTemp.tmpStore); err != nil { return err } - if _, err := tmpStore.Persist(); err != nil { + if err := bcTemp.validators.commit(bcTemp.tmpStore); err != nil { + return err + } + if _, err := bcTemp.tmpStore.Persist(); err != nil { return err } @@ -566,6 +605,67 @@ func (bc *Blockchain) storeBlock(block *Block) error { return nil } +func processValidatorStateDescriptor(descriptor *transaction.StateDescriptor, bcTemp *BlockChainTemp) error { + publicKey := &keys.PublicKey{} + err := publicKey.DecodeBytes(descriptor.Key) + if err != nil { + return err + } + validatorState, err := bcTemp.validators.getAndUpdate(bcTemp.tmpStore, publicKey) + if err != nil { + return err + } + if descriptor.Field == "Registered" { + isRegistered, err := strconv.ParseBool(string(descriptor.Value)) + if err != nil { + return err + } + validatorState.Registered = isRegistered + } + return nil +} + +func processAccountStateDescriptor(descriptor *transaction.StateDescriptor, bcTemp *BlockChainTemp) error { + hash, err := util.Uint160DecodeBytes(descriptor.Key) + if err != nil { + return err + } + account, err := bcTemp.accounts.getAndUpdate(bcTemp.tmpStore, hash) + if err != nil { + return err + } + + if descriptor.Field == "Votes" { + balance := account.GetBalanceValues()[governingTokenTX().Hash()] + for _, vote := range account.Votes { + validator, err := bcTemp.validators.getAndUpdate(bcTemp.tmpStore, vote) + if err != nil { + return err + } + validator.Votes -= balance + if !validator.RegisteredAndHasVotes() { + delete(bcTemp.validators, vote) + } + } + + votes := keys.PublicKeys{} + err := votes.DecodeBytes(descriptor.Value) + if err != nil { + return err + } + if votes.Len() != len(account.Votes) { + account.Votes = votes + for _, vote := range votes { + _, err := bcTemp.validators.getAndUpdate(bcTemp.tmpStore, vote) + if err != nil { + return err + } + } + } + } + return nil +} + // persist flushes current in-memory store contents to the persistent storage. func (bc *Blockchain) persist() error { var ( @@ -1132,6 +1232,168 @@ func (bc *Blockchain) GetScriptHashesForVerifyingClaim(t *transaction.Transactio return nil, fmt.Errorf("no hashes found") } +//GetStandByValidators returns validators from the configuration. +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) { + bcTemp := bc.NewBlockChainTemp() + if len(txes) > 0 { + for _, tx := range txes { + // iterate through outputs + for index, output := range tx.Outputs { + accountState := bc.GetAccountState(output.ScriptHash) + accountState.Balances[output.AssetID] = append(accountState.Balances[output.AssetID], UnspentBalance{ + Tx: tx.Hash(), + Index: uint16(index), + Value: output.Amount, + }) + if output.AssetID.Equals(governingTokenTX().Hash()) && len(accountState.Votes) > 0 { + for _, vote := range accountState.Votes { + validatorState, err := bcTemp.validators.getAndUpdate(bcTemp.tmpStore, vote) + if err != nil { + return nil, err + } + validatorState.Votes += output.Amount + } + } + } + + // group inputs by the same previous hash and iterate through inputs + group := make(map[util.Uint256][]*transaction.Input) + for _, input := range tx.Inputs { + group[input.PrevHash] = append(group[input.PrevHash], input) + } + + for hash, inputs := range group { + prevTx, _, err := bc.GetTransaction(hash) + if err != nil { + return nil, err + } + for _, input := range inputs { + prevOutput := prevTx.Outputs[input.PrevIndex] + accountState, err := bcTemp.accounts.getAndUpdate(bcTemp.tmpStore, prevOutput.ScriptHash) + if err != nil { + return nil, err + } + + if prevOutput.AssetID.Equals(governingTokenTX().Hash()) { + if len(accountState.Votes) > 0 { + for _, vote := range accountState.Votes { + validatorState, err := bcTemp.validators.getAndUpdate(bcTemp.tmpStore, vote) + if err != nil { + return nil, err + } + validatorState.Votes -= prevOutput.Amount + if !validatorState.Registered && validatorState.Votes.Equal(util.Fixed8(0)) { + delete(bcTemp.validators, vote) + } + } + } + } + delete(accountState.Balances, prevOutput.AssetID) + } + } + + switch t := tx.Data.(type) { + case *transaction.EnrollmentTX: + validatorState, err := bcTemp.validators.getAndUpdate(bcTemp.tmpStore, t.PublicKey) + if err != nil { + return nil, err + } + validatorState.Registered = true + + case *transaction.StateTX: + for _, desc := range t.Descriptors { + switch desc.Type { + case transaction.Account: + err := processAccountStateDescriptor(desc, bcTemp) + if err != nil { + return nil, err + } + case transaction.Validator: + err := processValidatorStateDescriptor(desc, bcTemp) + if err != nil { + return nil, err + } + } + } + } + } + } + + validators := getValidatorsFromStore(bcTemp.tmpStore) + for _, validator := range validators { + if validator.Votes > util.Fixed8(0) { + // Select count of votes with strange logic: + // int count = (int)snapshot.ValidatorsCount.Get().Votes.Select((p, i) => new + // { + // Count = i, + // Votes = p + // }).Where(p => p.Votes > Fixed8.Zero).ToArray().WeightedFilter(0.25, 0.75, p => p.Votes.GetData(), (p, w) => new + // { + // p.Count, + // Weight = w + // }).WeightedAverage(p => p.Count, p => p.Weight); + // count = Math.Max(count, Blockchain.StandbyValidators.Length); + } + } + var count int + count = 0 + standByValidators, err := bc.GetStandByValidators() + if err != nil { + return nil, err + } + if count < len(standByValidators) { + count = len(standByValidators) + } + + uniqueSBValidators := standByValidators.Unique() + pubKeys := keys.PublicKeys{} + for _, validator := range validators { + if validator.RegisteredAndHasVotes() || uniqueSBValidators.Contains(validator.PublicKey) { + pubKeys = append(pubKeys, validator.PublicKey) + } + } + sort.Sort(sort.Reverse(pubKeys)) + if pubKeys.Len() >= count { + return pubKeys[:count], nil + } + + result := pubKeys.Unique() + for i := 0; i < uniqueSBValidators.Len() && result.Len() < count; i++ { + result = append(result, uniqueSBValidators[i]) + } + return result, nil +} + +// BlockChainTemp represents Blockchain temporarily structure with mempool. +type BlockChainTemp struct { + tmpStore *storage.MemCachedStore + unspentCoins UnspentCoins + spentCoins SpentCoins + accounts Accounts + assets Assets + contracts Contracts + validators Validators +} + +// NewBlockChainTemp creates temporarily blockchain state with it's store. +func (bc *Blockchain) NewBlockChainTemp() *BlockChainTemp { + return &BlockChainTemp{ + tmpStore: bc.store, + unspentCoins: make(UnspentCoins), + spentCoins: make(SpentCoins), + accounts: make(Accounts), + assets: make(Assets), + contracts: make(Contracts), + validators: make(Validators), + } +} + // GetScriptHashesForVerifying returns all the ScriptHashes of a transaction which will be use // to verify whether the transaction is bonafide or not. // Golang implementation of GetScriptHashesForVerifying method in C# (https://github.com/neo-project/neo/blob/master/neo/Network/P2P/Payloads/Transaction.cs#L190) diff --git a/pkg/core/blockchainer.go b/pkg/core/blockchainer.go index c13fd1ae3..0531d683d 100644 --- a/pkg/core/blockchainer.go +++ b/pkg/core/blockchainer.go @@ -4,6 +4,7 @@ import ( "github.com/CityOfZion/neo-go/config" "github.com/CityOfZion/neo-go/pkg/core/storage" "github.com/CityOfZion/neo-go/pkg/core/transaction" + "github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/vm" ) @@ -27,6 +28,7 @@ type Blockchainer interface { HasTransaction(util.Uint256) bool GetAssetState(util.Uint256) *AssetState GetAccountState(util.Uint160) *AccountState + GetValidators(txes... *transaction.Transaction) ([]*keys.PublicKey, error) GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error) GetStorageItem(scripthash util.Uint160, key []byte) *StorageItem GetStorageItems(hash util.Uint160) (map[string]*StorageItem, error) diff --git a/pkg/core/interop_neo.go b/pkg/core/interop_neo.go index 1d41100b1..9c177c3e9 100644 --- a/pkg/core/interop_neo.go +++ b/pkg/core/interop_neo.go @@ -196,6 +196,16 @@ func (ic *interopContext) txGetWitnesses(v *vm.VM) error { return nil } +// bcGetValidators returns validators. +func (ic *interopContext) bcGetValidators(v *vm.VM) error { + validators, err := ic.bc.GetValidators() + if err != nil { + return err + } + 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 a8e1dfa7d..deafb6e8b 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -101,6 +101,7 @@ func (ic *interopContext) getNeoInteropMap() map[string]vm.InteropFuncPrice { "Neo.Blockchain.GetHeight": {Func: ic.bcGetHeight, Price: 1}, "Neo.Blockchain.GetTransaction": {Func: ic.bcGetTransaction, Price: 100}, "Neo.Blockchain.GetTransactionHeight": {Func: ic.bcGetTransactionHeight, Price: 100}, + "Neo.Blockchain.GetValidators": {Func: ic.bcGetValidators, Price: 200}, "Neo.Contract.Create": {Func: ic.contractCreate, Price: 0}, "Neo.Contract.Destroy": {Func: ic.contractDestroy, Price: 1}, "Neo.Contract.GetScript": {Func: ic.contractGetScript, Price: 1}, @@ -139,7 +140,6 @@ func (ic *interopContext) getNeoInteropMap() map[string]vm.InteropFuncPrice { "Neo.Transaction.GetType": {Func: ic.txGetType, Price: 1}, "Neo.Transaction.GetUnspentCoins": {Func: ic.txGetUnspentCoins, Price: 200}, "Neo.Transaction.GetWitnesses": {Func: ic.txGetWitnesses, Price: 200}, - // "Neo.Blockchain.GetValidators": {Func: ic.bcGetValidators, Price: 200}, // "Neo.Enumerator.Concat": {Func: ic.enumeratorConcat, Price: 1}, // "Neo.Enumerator.Create": {Func: ic.enumeratorCreate, Price: 1}, // "Neo.Enumerator.Next": {Func: ic.enumeratorNext, Price: 1}, @@ -185,6 +185,7 @@ func (ic *interopContext) getNeoInteropMap() map[string]vm.InteropFuncPrice { "AntShares.Blockchain.GetHeader": {Func: ic.bcGetHeader, Price: 100}, "AntShares.Blockchain.GetHeight": {Func: ic.bcGetHeight, Price: 1}, "AntShares.Blockchain.GetTransaction": {Func: ic.bcGetTransaction, Price: 100}, + "AntShares.Blockchain.GetValidators": {Func: ic.bcGetValidators, Price: 200}, "AntShares.Contract.Create": {Func: ic.contractCreate, Price: 0}, "AntShares.Contract.Destroy": {Func: ic.contractDestroy, Price: 1}, "AntShares.Contract.GetScript": {Func: ic.contractGetScript, Price: 1}, @@ -215,6 +216,5 @@ func (ic *interopContext) getNeoInteropMap() map[string]vm.InteropFuncPrice { "AntShares.Transaction.GetOutputs": {Func: ic.txGetOutputs, Price: 1}, "AntShares.Transaction.GetReferences": {Func: ic.txGetReferences, Price: 200}, "AntShares.Transaction.GetType": {Func: ic.txGetType, Price: 1}, - // "AntShares.Blockchain.GetValidators": {Func: ic.bcGetValidators, Price: 200}, } } diff --git a/pkg/core/validator_state.go b/pkg/core/validator_state.go index b93585e99..98aa01491 100644 --- a/pkg/core/validator_state.go +++ b/pkg/core/validator_state.go @@ -1,16 +1,109 @@ package core import ( + "fmt" + + "github.com/CityOfZion/neo-go/pkg/core/storage" "github.com/CityOfZion/neo-go/pkg/crypto/keys" + "github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/util" ) // Validators is a mapping between public keys and ValidatorState. type Validators map[*keys.PublicKey]*ValidatorState +func (v Validators) getAndUpdate(s storage.Store, publicKey *keys.PublicKey) (*ValidatorState, error) { + if validator, ok := v[publicKey]; ok { + return validator, nil + } + + validatorState, err := getValidatorStateFromStore(s, publicKey) + if err != nil { + if err != storage.ErrKeyNotFound { + return nil, err + } + validatorState = &ValidatorState{PublicKey: publicKey} + } + v[publicKey] = validatorState + return validatorState, nil + +} + +// getValidatorsFromStore returns all validators from store. +func getValidatorsFromStore(s storage.Store) []*ValidatorState { + var validators []*ValidatorState + s.Seek(storage.STValidator.Bytes(), func(k, v []byte) { + r := io.NewBinReaderFromBuf(v) + validator := &ValidatorState{} + validator.DecodeBinary(r) + if r.Err != nil { + return + } + validators = append(validators, validator) + }) + return validators +} + +// getValidatorStateFromStore returns validator by publicKey. +func getValidatorStateFromStore(s storage.Store, publicKey *keys.PublicKey) (*ValidatorState, error) { + validatorState := &ValidatorState{} + key := storage.AppendPrefix(storage.STValidator, publicKey.Bytes()) + if b, err := s.Get(key); err == nil { + r := io.NewBinReaderFromBuf(b) + validatorState.DecodeBinary(r) + if r.Err != nil { + return nil, fmt.Errorf("failed to decode (ValidatorState): %s", r.Err) + } + } else { + return nil, err + } + return validatorState, nil +} + +// commit writes all validator states to the given Batch. +func (v Validators) commit(store storage.Store) error { + for _, validator := range v { + if err := putValidatorStateIntoStore(store, validator); err != nil { + return err + } + } + return nil +} + +// putValidatorStateIntoStore puts given ValidatorState into the given store. +func putValidatorStateIntoStore(store storage.Store, vs *ValidatorState) error { + buf := io.NewBufBinWriter() + vs.EncodeBinary(buf.BinWriter) + if buf.Err != nil { + return buf.Err + } + key := storage.AppendPrefix(storage.STValidator, vs.PublicKey.Bytes()) + return store.Put(key, buf.Bytes()) +} + // ValidatorState holds the state of a validator. type ValidatorState struct { PublicKey *keys.PublicKey Registered bool Votes util.Fixed8 } + +// RegisteredAndHasVotes returns true or false whether Validator is registered and has votes. +func (vs *ValidatorState) RegisteredAndHasVotes() bool { + return vs.Registered && vs.Votes > util.Fixed8(0) +} + +// EncodeBinary encodes ValidatorState to the given BinWriter. +func (vs *ValidatorState) EncodeBinary(bw *io.BinWriter) { + vs.PublicKey.EncodeBinary(bw) + bw.WriteLE(vs.Registered) + bw.WriteLE(vs.Votes) +} + +// DecodeBinary decodes ValidatorState from the given BinReader. +func (vs *ValidatorState) DecodeBinary(reader *io.BinReader) { + vs.PublicKey = &keys.PublicKey{} + vs.PublicKey.DecodeBinary(reader) + reader.ReadLE(&vs.Registered) + reader.ReadLE(&vs.Votes) +} diff --git a/pkg/crypto/keys/publickey.go b/pkg/crypto/keys/publickey.go index 80af4d5ee..9a871e3c6 100644 --- a/pkg/crypto/keys/publickey.go +++ b/pkg/crypto/keys/publickey.go @@ -34,6 +34,34 @@ func (keys PublicKeys) Less(i, j int) bool { return keys[i].Y.Cmp(keys[j].Y) == -1 } +// DecodeBytes decodes a PublicKeys from the given slice of bytes. +func (keys PublicKeys) DecodeBytes(data []byte) error { + b := io.NewBinReaderFromBuf(data) + b.ReadArray(keys) + return b.Err +} + +// Contains checks whether passed param contained in PublicKeys. +func (keys PublicKeys) Contains(pKey *PublicKey) bool { + for _, key := range keys { + if key.Equal(pKey) { + return true + } + } + return false +} + +// Unique returns set of public keys. +func (keys PublicKeys) Unique() PublicKeys { + unique := PublicKeys{} + for _, publicKey := range keys { + if !unique.Contains(publicKey) { + unique = append(unique, publicKey) + } + } + return unique +} + // PublicKey represents a public key and provides a high level // API around the X/Y point. type PublicKey struct { @@ -41,6 +69,10 @@ type PublicKey struct { Y *big.Int } +func (p *PublicKey) Equal(key *PublicKey) bool { + return p.X.Cmp(key.X) == 0 && p.Y.Cmp(key.Y) == 0 +} + // NewPublicKeyFromString returns a public key created from the // given hex string. func NewPublicKeyFromString(s string) (*PublicKey, error) { diff --git a/pkg/network/helper_test.go b/pkg/network/helper_test.go index 4d64656e8..14daf897e 100644 --- a/pkg/network/helper_test.go +++ b/pkg/network/helper_test.go @@ -11,6 +11,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core/storage" "github.com/CityOfZion/neo-go/pkg/core/transaction" + "github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/network/payload" "github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/vm" @@ -77,6 +78,9 @@ func (chain testChain) GetAssetState(util.Uint256) *core.AssetState { func (chain testChain) GetAccountState(util.Uint160) *core.AccountState { panic("TODO") } +func (chain testChain) GetValidators(...*transaction.Transaction) ([]*keys.PublicKey, error) { + panic("TODO") +} func (chain testChain) GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error) { panic("TODO") }