Merge pull request #508 from nspcc-dev/feature/getvalidators_420
core: Implement getvalidators interop
This commit is contained in:
commit
eb84ae49da
12 changed files with 775 additions and 109 deletions
|
@ -6,12 +6,14 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/config"
|
"github.com/CityOfZion/neo-go/config"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
"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/io"
|
||||||
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
@ -336,41 +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
|
// 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.
|
// and all tests are in place, we can make a more optimized and cleaner implementation.
|
||||||
func (bc *Blockchain) storeBlock(block *Block) error {
|
func (bc *Blockchain) storeBlock(block *Block) error {
|
||||||
var (
|
chainState := NewBlockChainState(bc.store)
|
||||||
tmpStore = storage.NewMemCachedStore(bc.store)
|
|
||||||
unspentCoins = make(UnspentCoins)
|
|
||||||
spentCoins = make(SpentCoins)
|
|
||||||
accounts = make(Accounts)
|
|
||||||
assets = make(Assets)
|
|
||||||
contracts = make(Contracts)
|
|
||||||
)
|
|
||||||
|
|
||||||
if err := storeAsBlock(tmpStore, block, 0); err != nil {
|
if err := chainState.storeAsBlock(block, 0); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := storeAsCurrentBlock(tmpStore, block); err != nil {
|
if err := chainState.storeAsCurrentBlock(block); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tx := range block.Transactions {
|
for _, tx := range block.Transactions {
|
||||||
if err := storeAsTransaction(tmpStore, tx, block.Index); err != nil {
|
if err := chainState.storeAsTransaction(tx, block.Index); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
unspentCoins[tx.Hash()] = NewUnspentCoinState(len(tx.Outputs))
|
chainState.unspentCoins[tx.Hash()] = NewUnspentCoinState(len(tx.Outputs))
|
||||||
|
|
||||||
// Process TX outputs.
|
// Process TX outputs.
|
||||||
for index, output := range tx.Outputs {
|
if err := processOutputs(tx, chainState); err != nil {
|
||||||
account, err := accounts.getAndUpdate(bc.store, output.ScriptHash)
|
return err
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
account.Balances[output.AssetID] = append(account.Balances[output.AssetID], UnspentBalance{
|
|
||||||
Tx: tx.Hash(),
|
|
||||||
Index: uint16(index),
|
|
||||||
Value: output.Amount,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process TX inputs that are grouped by previous hash.
|
// Process TX inputs that are grouped by previous hash.
|
||||||
|
@ -380,14 +367,14 @@ func (bc *Blockchain) storeBlock(block *Block) error {
|
||||||
return fmt.Errorf("could not find previous TX: %s", prevHash)
|
return fmt.Errorf("could not find previous TX: %s", prevHash)
|
||||||
}
|
}
|
||||||
for _, input := range inputs {
|
for _, input := range inputs {
|
||||||
unspent, err := unspentCoins.getAndUpdate(bc.store, input.PrevHash)
|
unspent, err := chainState.unspentCoins.getAndUpdate(chainState.store, input.PrevHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
unspent.states[input.PrevIndex] = CoinStateSpent
|
unspent.states[input.PrevIndex] = CoinStateSpent
|
||||||
|
|
||||||
prevTXOutput := prevTX.Outputs[input.PrevIndex]
|
prevTXOutput := prevTX.Outputs[input.PrevIndex]
|
||||||
account, err := accounts.getAndUpdate(bc.store, prevTXOutput.ScriptHash)
|
account, err := chainState.accounts.getAndUpdate(chainState.store, prevTXOutput.ScriptHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -395,7 +382,20 @@ func (bc *Blockchain) storeBlock(block *Block) error {
|
||||||
if prevTXOutput.AssetID.Equals(governingTokenTX().Hash()) {
|
if prevTXOutput.AssetID.Equals(governingTokenTX().Hash()) {
|
||||||
spentCoin := NewSpentCoinState(input.PrevHash, prevTXHeight)
|
spentCoin := NewSpentCoinState(input.PrevHash, prevTXHeight)
|
||||||
spentCoin.items[input.PrevIndex] = block.Index
|
spentCoin.items[input.PrevIndex] = block.Index
|
||||||
spentCoins[input.PrevHash] = spentCoin
|
chainState.spentCoins[input.PrevHash] = spentCoin
|
||||||
|
|
||||||
|
if len(account.Votes) > 0 {
|
||||||
|
for _, vote := range account.Votes {
|
||||||
|
validator, err := chainState.validators.getAndUpdate(chainState.store, vote)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
validator.Votes -= prevTXOutput.Amount
|
||||||
|
if !validator.RegisteredAndHasVotes() {
|
||||||
|
delete(chainState.validators, vote)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
balancesLen := len(account.Balances[prevTXOutput.AssetID])
|
balancesLen := len(account.Balances[prevTXOutput.AssetID])
|
||||||
|
@ -419,7 +419,7 @@ func (bc *Blockchain) storeBlock(block *Block) error {
|
||||||
// Process the underlying type of the TX.
|
// Process the underlying type of the TX.
|
||||||
switch t := tx.Data.(type) {
|
switch t := tx.Data.(type) {
|
||||||
case *transaction.RegisterTX:
|
case *transaction.RegisterTX:
|
||||||
assets[tx.Hash()] = &AssetState{
|
chainState.assets[tx.Hash()] = &AssetState{
|
||||||
ID: tx.Hash(),
|
ID: tx.Hash(),
|
||||||
AssetType: t.AssetType,
|
AssetType: t.AssetType,
|
||||||
Name: t.Name,
|
Name: t.Name,
|
||||||
|
@ -434,7 +434,7 @@ func (bc *Blockchain) storeBlock(block *Block) error {
|
||||||
if res.Amount < 0 {
|
if res.Amount < 0 {
|
||||||
var asset *AssetState
|
var asset *AssetState
|
||||||
|
|
||||||
asset, ok := assets[res.AssetID]
|
asset, ok := chainState.assets[res.AssetID]
|
||||||
if !ok {
|
if !ok {
|
||||||
asset = bc.GetAssetState(res.AssetID)
|
asset = bc.GetAssetState(res.AssetID)
|
||||||
}
|
}
|
||||||
|
@ -442,14 +442,14 @@ func (bc *Blockchain) storeBlock(block *Block) error {
|
||||||
return fmt.Errorf("issue failed: no asset %s", res.AssetID)
|
return fmt.Errorf("issue failed: no asset %s", res.AssetID)
|
||||||
}
|
}
|
||||||
asset.Available -= res.Amount
|
asset.Available -= res.Amount
|
||||||
assets[res.AssetID] = asset
|
chainState.assets[res.AssetID] = asset
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case *transaction.ClaimTX:
|
case *transaction.ClaimTX:
|
||||||
// Remove claimed NEO from spent coins making it unavalaible for
|
// Remove claimed NEO from spent coins making it unavalaible for
|
||||||
// additional claims.
|
// additional claims.
|
||||||
for _, input := range t.Claims {
|
for _, input := range t.Claims {
|
||||||
scs, err := spentCoins.getAndUpdate(bc.store, input.PrevHash)
|
scs, err := chainState.spentCoins.getAndUpdate(bc.store, input.PrevHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -458,11 +458,17 @@ func (bc *Blockchain) storeBlock(block *Block) error {
|
||||||
delete(scs.items, input.PrevIndex)
|
delete(scs.items, input.PrevIndex)
|
||||||
} else {
|
} else {
|
||||||
// Uninitialized, new, forget about it.
|
// Uninitialized, new, forget about it.
|
||||||
delete(spentCoins, input.PrevHash)
|
delete(chainState.spentCoins, input.PrevHash)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case *transaction.EnrollmentTX:
|
case *transaction.EnrollmentTX:
|
||||||
|
if err := processEnrollmentTX(chainState, t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
case *transaction.StateTX:
|
case *transaction.StateTX:
|
||||||
|
if err := processStateTX(chainState, t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
case *transaction.PublishTX:
|
case *transaction.PublishTX:
|
||||||
var properties smartcontract.PropertyState
|
var properties smartcontract.PropertyState
|
||||||
if t.NeedStorage {
|
if t.NeedStorage {
|
||||||
|
@ -479,16 +485,15 @@ func (bc *Blockchain) storeBlock(block *Block) error {
|
||||||
Email: t.Email,
|
Email: t.Email,
|
||||||
Description: t.Description,
|
Description: t.Description,
|
||||||
}
|
}
|
||||||
contracts[contract.ScriptHash()] = contract
|
chainState.contracts[contract.ScriptHash()] = contract
|
||||||
|
|
||||||
case *transaction.InvocationTX:
|
case *transaction.InvocationTX:
|
||||||
systemInterop := newInteropContext(0x10, bc, tmpStore, block, tx)
|
systemInterop := newInteropContext(0x10, bc, chainState.store, block, tx)
|
||||||
v := bc.spawnVMWithInterops(systemInterop)
|
v := bc.spawnVMWithInterops(systemInterop)
|
||||||
v.SetCheckedHash(tx.VerificationHash().Bytes())
|
v.SetCheckedHash(tx.VerificationHash().Bytes())
|
||||||
v.LoadScript(t.Script)
|
v.LoadScript(t.Script)
|
||||||
err := v.Run()
|
err := v.Run()
|
||||||
if !v.HasFailed() {
|
if !v.HasFailed() {
|
||||||
_, err = systemInterop.mem.Persist()
|
_, err := systemInterop.mem.Persist()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to persist invocation results")
|
return errors.Wrap(err, "failed to persist invocation results")
|
||||||
}
|
}
|
||||||
|
@ -531,30 +536,14 @@ func (bc *Blockchain) storeBlock(block *Block) error {
|
||||||
Stack: v.Stack("estack"),
|
Stack: v.Stack("estack"),
|
||||||
Events: systemInterop.notifications,
|
Events: systemInterop.notifications,
|
||||||
}
|
}
|
||||||
err = putAppExecResultIntoStore(tmpStore, aer)
|
err = putAppExecResultIntoStore(chainState.store, aer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to store notifications")
|
return errors.Wrap(err, "failed to store notifications")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Persist all to storage.
|
if err := chainState.commit(); err != nil {
|
||||||
if err := accounts.commit(tmpStore); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := unspentCoins.commit(tmpStore); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := spentCoins.commit(tmpStore); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := assets.commit(tmpStore); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := contracts.commit(tmpStore); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := tmpStore.Persist(); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -566,6 +555,92 @@ func (bc *Blockchain) storeBlock(block *Block) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// processOutputs processes transaction outputs.
|
||||||
|
func processOutputs(tx *transaction.Transaction, chainState *BlockChainState) error {
|
||||||
|
for index, output := range tx.Outputs {
|
||||||
|
account, err := chainState.accounts.getAndUpdate(chainState.store, output.ScriptHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
account.Balances[output.AssetID] = append(account.Balances[output.AssetID], UnspentBalance{
|
||||||
|
Tx: tx.Hash(),
|
||||||
|
Index: uint16(index),
|
||||||
|
Value: output.Amount,
|
||||||
|
})
|
||||||
|
if output.AssetID.Equals(governingTokenTX().Hash()) && len(account.Votes) > 0 {
|
||||||
|
for _, vote := range account.Votes {
|
||||||
|
validatorState, err := chainState.validators.getAndUpdate(chainState.store, vote)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
validatorState.Votes += output.Amount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func processValidatorStateDescriptor(descriptor *transaction.StateDescriptor, state *BlockChainState) error {
|
||||||
|
publicKey := &keys.PublicKey{}
|
||||||
|
err := publicKey.DecodeBytes(descriptor.Key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
validatorState, err := state.validators.getAndUpdate(state.store, 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, state *BlockChainState) error {
|
||||||
|
hash, err := util.Uint160DecodeBytes(descriptor.Key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
account, err := state.accounts.getAndUpdate(state.store, hash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if descriptor.Field == "Votes" {
|
||||||
|
balance := account.GetBalanceValues()[governingTokenTX().Hash()]
|
||||||
|
for _, vote := range account.Votes {
|
||||||
|
validator, err := state.validators.getAndUpdate(state.store, vote)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
validator.Votes -= balance
|
||||||
|
if !validator.RegisteredAndHasVotes() {
|
||||||
|
delete(state.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 := state.validators.getAndUpdate(state.store, vote)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// persist flushes current in-memory store contents to the persistent storage.
|
// persist flushes current in-memory store contents to the persistent storage.
|
||||||
func (bc *Blockchain) persist() error {
|
func (bc *Blockchain) persist() error {
|
||||||
var (
|
var (
|
||||||
|
@ -1134,6 +1209,142 @@ func (bc *Blockchain) GetScriptHashesForVerifyingClaim(t *transaction.Transactio
|
||||||
return nil, fmt.Errorf("no hashes found")
|
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) {
|
||||||
|
chainState := NewBlockChainState(bc.store)
|
||||||
|
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 := chainState.validators.getAndUpdate(chainState.store, 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
|
||||||
|
}
|
||||||
|
// process inputs
|
||||||
|
for _, input := range inputs {
|
||||||
|
prevOutput := prevTx.Outputs[input.PrevIndex]
|
||||||
|
accountState, err := chainState.accounts.getAndUpdate(chainState.store, prevOutput.ScriptHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// process account state votes: if there are any -> validators will be updated.
|
||||||
|
if prevOutput.AssetID.Equals(governingTokenTX().Hash()) {
|
||||||
|
if len(accountState.Votes) > 0 {
|
||||||
|
for _, vote := range accountState.Votes {
|
||||||
|
validatorState, err := chainState.validators.getAndUpdate(chainState.store, vote)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
validatorState.Votes -= prevOutput.Amount
|
||||||
|
if !validatorState.Registered && validatorState.Votes.Equal(util.Fixed8(0)) {
|
||||||
|
delete(chainState.validators, vote)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(accountState.Balances, prevOutput.AssetID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t := tx.Data.(type) {
|
||||||
|
case *transaction.EnrollmentTX:
|
||||||
|
if err := processEnrollmentTX(chainState, t); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case *transaction.StateTX:
|
||||||
|
if err := processStateTX(chainState, t); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validators := getValidatorsFromStore(chainState.store)
|
||||||
|
|
||||||
|
count := GetValidatorsWeightedAverage(validators)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func processStateTX(chainState *BlockChainState, tx *transaction.StateTX, ) error {
|
||||||
|
for _, desc := range tx.Descriptors {
|
||||||
|
switch desc.Type {
|
||||||
|
case transaction.Account:
|
||||||
|
if err := processAccountStateDescriptor(desc, chainState); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case transaction.Validator:
|
||||||
|
if err := processValidatorStateDescriptor(desc, chainState); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func processEnrollmentTX(chainState *BlockChainState, tx *transaction.EnrollmentTX) error {
|
||||||
|
validatorState, err := chainState.validators.getAndUpdate(chainState.store, tx.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
validatorState.Registered = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetScriptHashesForVerifying returns all the ScriptHashes of a transaction which will be use
|
// GetScriptHashesForVerifying returns all the ScriptHashes of a transaction which will be use
|
||||||
// to verify whether the transaction is bonafide or not.
|
// 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)
|
// Golang implementation of GetScriptHashesForVerifying method in C# (https://github.com/neo-project/neo/blob/master/neo/Network/P2P/Payloads/Transaction.cs#L190)
|
||||||
|
|
96
pkg/core/blockchain_state.go
Normal file
96
pkg/core/blockchain_state.go
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BlockChainState represents Blockchain state structure with mempool.
|
||||||
|
type BlockChainState struct {
|
||||||
|
store *storage.MemCachedStore
|
||||||
|
unspentCoins UnspentCoins
|
||||||
|
spentCoins SpentCoins
|
||||||
|
accounts Accounts
|
||||||
|
assets Assets
|
||||||
|
contracts Contracts
|
||||||
|
validators Validators
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBlockChainState creates blockchain state with it's memchached store.
|
||||||
|
func NewBlockChainState(store *storage.MemCachedStore) *BlockChainState {
|
||||||
|
return &BlockChainState{
|
||||||
|
store: store,
|
||||||
|
unspentCoins: make(UnspentCoins),
|
||||||
|
spentCoins: make(SpentCoins),
|
||||||
|
accounts: make(Accounts),
|
||||||
|
assets: make(Assets),
|
||||||
|
contracts: make(Contracts),
|
||||||
|
validators: make(Validators),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// commit commits all the data in current state into storage.
|
||||||
|
func (state *BlockChainState) commit() error {
|
||||||
|
if err := state.accounts.commit(state.store); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := state.unspentCoins.commit(state.store); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := state.spentCoins.commit(state.store); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := state.assets.commit(state.store); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := state.contracts.commit(state.store); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := state.validators.commit(state.store); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := state.store.Persist(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// storeAsBlock stores the given block as DataBlock.
|
||||||
|
func (state *BlockChainState) storeAsBlock(block *Block, sysFee uint32) error {
|
||||||
|
var (
|
||||||
|
key = storage.AppendPrefix(storage.DataBlock, block.Hash().BytesReverse())
|
||||||
|
buf = io.NewBufBinWriter()
|
||||||
|
)
|
||||||
|
// sysFee needs to be handled somehow
|
||||||
|
// buf.WriteLE(sysFee)
|
||||||
|
b, err := block.Trim()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buf.WriteLE(b)
|
||||||
|
if buf.Err != nil {
|
||||||
|
return buf.Err
|
||||||
|
}
|
||||||
|
return state.store.Put(key, buf.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// storeAsCurrentBlock stores the given block witch prefix SYSCurrentBlock.
|
||||||
|
func (state *BlockChainState) storeAsCurrentBlock(block *Block) error {
|
||||||
|
buf := io.NewBufBinWriter()
|
||||||
|
buf.WriteLE(block.Hash().BytesReverse())
|
||||||
|
buf.WriteLE(block.Index)
|
||||||
|
return state.store.Put(storage.SYSCurrentBlock.Bytes(), buf.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// storeAsTransaction stores the given TX as DataTransaction.
|
||||||
|
func (state *BlockChainState) storeAsTransaction(tx *transaction.Transaction, index uint32) error {
|
||||||
|
key := storage.AppendPrefix(storage.DataTransaction, tx.Hash().BytesReverse())
|
||||||
|
buf := io.NewBufBinWriter()
|
||||||
|
buf.WriteLE(index)
|
||||||
|
tx.EncodeBinary(buf.BinWriter)
|
||||||
|
if buf.Err != nil {
|
||||||
|
return buf.Err
|
||||||
|
}
|
||||||
|
return state.store.Put(key, buf.Bytes())
|
||||||
|
}
|
46
pkg/core/blockchain_state_test.go
Normal file
46
pkg/core/blockchain_state_test.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewBlockChainStateAndCommit(t *testing.T) {
|
||||||
|
memCachedStore := storage.NewMemCachedStore(storage.NewMemoryStore())
|
||||||
|
bcState := NewBlockChainState(memCachedStore)
|
||||||
|
err := bcState.commit()
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStoreAsBlock(t *testing.T) {
|
||||||
|
memCachedStore := storage.NewMemCachedStore(storage.NewMemoryStore())
|
||||||
|
bcState := NewBlockChainState(memCachedStore)
|
||||||
|
|
||||||
|
block := newBlock(0, newMinerTX())
|
||||||
|
err := bcState.storeAsBlock(block, 0)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStoreAsCurrentBlock(t *testing.T) {
|
||||||
|
memCachedStore := storage.NewMemCachedStore(storage.NewMemoryStore())
|
||||||
|
bcState := NewBlockChainState(memCachedStore)
|
||||||
|
|
||||||
|
block := newBlock(0, newMinerTX())
|
||||||
|
err := bcState.storeAsCurrentBlock(block)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStoreAsTransaction(t *testing.T) {
|
||||||
|
memCachedStore := storage.NewMemCachedStore(storage.NewMemoryStore())
|
||||||
|
bcState := NewBlockChainState(memCachedStore)
|
||||||
|
|
||||||
|
tx := &transaction.Transaction{
|
||||||
|
Type: transaction.MinerType,
|
||||||
|
Data: &transaction.MinerTX{},
|
||||||
|
}
|
||||||
|
err := bcState.storeAsTransaction(tx, 0)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"github.com/CityOfZion/neo-go/config"
|
"github.com/CityOfZion/neo-go/config"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
"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/util"
|
||||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||||
)
|
)
|
||||||
|
@ -27,6 +28,7 @@ type Blockchainer interface {
|
||||||
HasTransaction(util.Uint256) bool
|
HasTransaction(util.Uint256) bool
|
||||||
GetAssetState(util.Uint256) *AssetState
|
GetAssetState(util.Uint256) *AssetState
|
||||||
GetAccountState(util.Uint160) *AccountState
|
GetAccountState(util.Uint160) *AccountState
|
||||||
|
GetValidators(txes... *transaction.Transaction) ([]*keys.PublicKey, error)
|
||||||
GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error)
|
GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error)
|
||||||
GetStorageItem(scripthash util.Uint160, key []byte) *StorageItem
|
GetStorageItem(scripthash util.Uint160, key []byte) *StorageItem
|
||||||
GetStorageItems(hash util.Uint160) (map[string]*StorageItem, error)
|
GetStorageItems(hash util.Uint160) (map[string]*StorageItem, error)
|
||||||
|
|
|
@ -196,6 +196,16 @@ func (ic *interopContext) txGetWitnesses(v *vm.VM) error {
|
||||||
return nil
|
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.
|
// popInputFromVM returns transaction.Input from the first estack element.
|
||||||
func popInputFromVM(v *vm.VM) (*transaction.Input, error) {
|
func popInputFromVM(v *vm.VM) (*transaction.Input, error) {
|
||||||
inInterface := v.Estack().Pop().Value()
|
inInterface := v.Estack().Pop().Value()
|
||||||
|
|
|
@ -101,6 +101,7 @@ func (ic *interopContext) getNeoInteropMap() map[string]vm.InteropFuncPrice {
|
||||||
"Neo.Blockchain.GetHeight": {Func: ic.bcGetHeight, Price: 1},
|
"Neo.Blockchain.GetHeight": {Func: ic.bcGetHeight, Price: 1},
|
||||||
"Neo.Blockchain.GetTransaction": {Func: ic.bcGetTransaction, Price: 100},
|
"Neo.Blockchain.GetTransaction": {Func: ic.bcGetTransaction, Price: 100},
|
||||||
"Neo.Blockchain.GetTransactionHeight": {Func: ic.bcGetTransactionHeight, 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.Create": {Func: ic.contractCreate, Price: 0},
|
||||||
"Neo.Contract.Destroy": {Func: ic.contractDestroy, Price: 1},
|
"Neo.Contract.Destroy": {Func: ic.contractDestroy, Price: 1},
|
||||||
"Neo.Contract.GetScript": {Func: ic.contractGetScript, 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.GetType": {Func: ic.txGetType, Price: 1},
|
||||||
"Neo.Transaction.GetUnspentCoins": {Func: ic.txGetUnspentCoins, Price: 200},
|
"Neo.Transaction.GetUnspentCoins": {Func: ic.txGetUnspentCoins, Price: 200},
|
||||||
"Neo.Transaction.GetWitnesses": {Func: ic.txGetWitnesses, 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.Concat": {Func: ic.enumeratorConcat, Price: 1},
|
||||||
// "Neo.Enumerator.Create": {Func: ic.enumeratorCreate, Price: 1},
|
// "Neo.Enumerator.Create": {Func: ic.enumeratorCreate, Price: 1},
|
||||||
// "Neo.Enumerator.Next": {Func: ic.enumeratorNext, 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.GetHeader": {Func: ic.bcGetHeader, Price: 100},
|
||||||
"AntShares.Blockchain.GetHeight": {Func: ic.bcGetHeight, Price: 1},
|
"AntShares.Blockchain.GetHeight": {Func: ic.bcGetHeight, Price: 1},
|
||||||
"AntShares.Blockchain.GetTransaction": {Func: ic.bcGetTransaction, Price: 100},
|
"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.Create": {Func: ic.contractCreate, Price: 0},
|
||||||
"AntShares.Contract.Destroy": {Func: ic.contractDestroy, Price: 1},
|
"AntShares.Contract.Destroy": {Func: ic.contractDestroy, Price: 1},
|
||||||
"AntShares.Contract.GetScript": {Func: ic.contractGetScript, 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.GetOutputs": {Func: ic.txGetOutputs, Price: 1},
|
||||||
"AntShares.Transaction.GetReferences": {Func: ic.txGetReferences, Price: 200},
|
"AntShares.Transaction.GetReferences": {Func: ic.txGetReferences, Price: 200},
|
||||||
"AntShares.Transaction.GetType": {Func: ic.txGetType, Price: 1},
|
"AntShares.Transaction.GetType": {Func: ic.txGetType, Price: 1},
|
||||||
// "AntShares.Blockchain.GetValidators": {Func: ic.bcGetValidators, Price: 200},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,9 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/config"
|
"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/core/transaction"
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
"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/smartcontract"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||||
|
@ -176,43 +174,3 @@ func headerSliceReverse(dest []*Header) {
|
||||||
dest[i], dest[j] = dest[j], dest[i]
|
dest[i], dest[j] = dest[j], dest[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// storeAsCurrentBlock stores the given block witch prefix
|
|
||||||
// SYSCurrentBlock.
|
|
||||||
func storeAsCurrentBlock(store storage.Store, block *Block) error {
|
|
||||||
buf := io.NewBufBinWriter()
|
|
||||||
buf.WriteLE(block.Hash().BytesReverse())
|
|
||||||
buf.WriteLE(block.Index)
|
|
||||||
return store.Put(storage.SYSCurrentBlock.Bytes(), buf.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
// storeAsBlock stores the given block as DataBlock.
|
|
||||||
func storeAsBlock(store storage.Store, block *Block, sysFee uint32) error {
|
|
||||||
var (
|
|
||||||
key = storage.AppendPrefix(storage.DataBlock, block.Hash().BytesReverse())
|
|
||||||
buf = io.NewBufBinWriter()
|
|
||||||
)
|
|
||||||
// sysFee needs to be handled somehow
|
|
||||||
// buf.WriteLE(sysFee)
|
|
||||||
b, err := block.Trim()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
buf.WriteLE(b)
|
|
||||||
if buf.Err != nil {
|
|
||||||
return buf.Err
|
|
||||||
}
|
|
||||||
return store.Put(key, buf.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
// storeAsTransaction stores the given TX as DataTransaction.
|
|
||||||
func storeAsTransaction(store storage.Store, tx *transaction.Transaction, index uint32) error {
|
|
||||||
key := storage.AppendPrefix(storage.DataTransaction, tx.Hash().BytesReverse())
|
|
||||||
buf := io.NewBufBinWriter()
|
|
||||||
buf.WriteLE(index)
|
|
||||||
tx.EncodeBinary(buf.BinWriter)
|
|
||||||
if buf.Err != nil {
|
|
||||||
return buf.Err
|
|
||||||
}
|
|
||||||
return store.Put(key, buf.Bytes())
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,16 +1,174 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/io"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Validators is a mapping between public keys and ValidatorState.
|
// Validators is a mapping between public keys and ValidatorState.
|
||||||
type Validators map[*keys.PublicKey]*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.
|
// ValidatorState holds the state of a validator.
|
||||||
type ValidatorState struct {
|
type ValidatorState struct {
|
||||||
PublicKey *keys.PublicKey
|
PublicKey *keys.PublicKey
|
||||||
Registered bool
|
Registered bool
|
||||||
Votes util.Fixed8
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValidatorsWeightedAverage applies weighted filter based on votes for validator and returns number of validators.
|
||||||
|
// Get back to it with further investigation in https://github.com/nspcc-dev/neo-go/issues/512.
|
||||||
|
func GetValidatorsWeightedAverage(validators []*ValidatorState) int {
|
||||||
|
return int(weightedAverage(applyWeightedFilter(validators)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyWeightedFilter is an implementation of the filter for validators votes.
|
||||||
|
// C# reference https://github.com/neo-project/neo/blob/41caff115c28d6c7665b2a7ac72967e7ce82e921/neo/Helper.cs#L273
|
||||||
|
func applyWeightedFilter(validators []*ValidatorState) map[*ValidatorState]float64 {
|
||||||
|
var validatorsWithVotes []*ValidatorState
|
||||||
|
var amount float64
|
||||||
|
|
||||||
|
weightedVotes := make(map[*ValidatorState]float64)
|
||||||
|
start := 0.25
|
||||||
|
end := 0.75
|
||||||
|
sum := float64(0)
|
||||||
|
current := float64(0)
|
||||||
|
|
||||||
|
for _, validator := range validators {
|
||||||
|
if validator.Votes > util.Fixed8(0) {
|
||||||
|
validatorsWithVotes = append(validatorsWithVotes, validator)
|
||||||
|
amount += validator.Votes.FloatValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, validator := range validatorsWithVotes {
|
||||||
|
if current >= end {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
weight := validator.Votes.FloatValue()
|
||||||
|
sum += weight
|
||||||
|
old := current
|
||||||
|
current = sum / amount
|
||||||
|
|
||||||
|
if current <= start {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if old < start {
|
||||||
|
if current > end {
|
||||||
|
weight = (end - start) * amount
|
||||||
|
} else {
|
||||||
|
weight = (current - start) * amount
|
||||||
|
}
|
||||||
|
} else if current > end {
|
||||||
|
weight = (end - old) * amount
|
||||||
|
}
|
||||||
|
weightedVotes[validator] = weight
|
||||||
|
}
|
||||||
|
return weightedVotes
|
||||||
|
}
|
||||||
|
|
||||||
|
func weightedAverage(weightedVotes map[*ValidatorState]float64) float64 {
|
||||||
|
sumWeight := float64(0)
|
||||||
|
sumValue := float64(0)
|
||||||
|
for vState, weight := range weightedVotes {
|
||||||
|
sumWeight += weight
|
||||||
|
sumValue += vState.Votes.FloatValue() * weight
|
||||||
|
}
|
||||||
|
if sumValue == 0 || sumWeight == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return sumValue / sumWeight
|
||||||
|
}
|
||||||
|
|
121
pkg/core/validator_state_test.go
Normal file
121
pkg/core/validator_state_test.go
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetAndUpdate(t *testing.T) {
|
||||||
|
store := storage.NewMemoryStore()
|
||||||
|
state1 := getDefaultValidator()
|
||||||
|
state2 := getDefaultValidator()
|
||||||
|
validators := make(Validators)
|
||||||
|
validators[state1.PublicKey] = state1
|
||||||
|
validators[state2.PublicKey] = state2
|
||||||
|
err := validators.commit(store)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
state, err := validators.getAndUpdate(store, state1.PublicKey)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, state1, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommit(t *testing.T) {
|
||||||
|
store := storage.NewMemoryStore()
|
||||||
|
state1 := getDefaultValidator()
|
||||||
|
state2 := getDefaultValidator()
|
||||||
|
validators := make(Validators)
|
||||||
|
validators[state1.PublicKey] = state1
|
||||||
|
validators[state2.PublicKey] = state2
|
||||||
|
err := validators.commit(store)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
validatorsFromStore := getValidatorsFromStore(store)
|
||||||
|
// 2 equal validators will be stored as 1 unique
|
||||||
|
require.Len(t, validatorsFromStore, 1)
|
||||||
|
require.Equal(t, state1, validatorsFromStore[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPutAndGet(t *testing.T) {
|
||||||
|
store := storage.NewMemoryStore()
|
||||||
|
state := getDefaultValidator()
|
||||||
|
err := putValidatorStateIntoStore(store, state)
|
||||||
|
require.NoError(t, err)
|
||||||
|
validatorFromStore, err := getValidatorStateFromStore(store, state.PublicKey)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, state.PublicKey, validatorFromStore.PublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFromStore_NoKey(t *testing.T) {
|
||||||
|
store := storage.NewMemoryStore()
|
||||||
|
state := getDefaultValidator()
|
||||||
|
_, err := getValidatorStateFromStore(store, state.PublicKey)
|
||||||
|
require.Errorf(t, err, "key not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidatorState_DecodeEncodeBinary(t *testing.T) {
|
||||||
|
state := &ValidatorState{
|
||||||
|
PublicKey: &keys.PublicKey{},
|
||||||
|
Registered: false,
|
||||||
|
Votes: util.Fixed8(10),
|
||||||
|
}
|
||||||
|
buf := io.NewBufBinWriter()
|
||||||
|
state.EncodeBinary(buf.BinWriter)
|
||||||
|
require.NoError(t, buf.Err)
|
||||||
|
|
||||||
|
decodedState := &ValidatorState{}
|
||||||
|
reader := io.NewBinReaderFromBuf(buf.Bytes())
|
||||||
|
decodedState.DecodeBinary(reader)
|
||||||
|
require.NoError(t, reader.Err)
|
||||||
|
require.Equal(t, state, decodedState)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegisteredAndHasVotes_Registered(t *testing.T) {
|
||||||
|
state := &ValidatorState{
|
||||||
|
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 := &ValidatorState{
|
||||||
|
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 := &ValidatorState{
|
||||||
|
PublicKey: &keys.PublicKey{
|
||||||
|
X: big.NewInt(1),
|
||||||
|
Y: big.NewInt(1),
|
||||||
|
},
|
||||||
|
Registered: false,
|
||||||
|
Votes: 1,
|
||||||
|
}
|
||||||
|
require.False(t, state.RegisteredAndHasVotes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultValidator() *ValidatorState {
|
||||||
|
return &ValidatorState{
|
||||||
|
PublicKey: &keys.PublicKey{},
|
||||||
|
Registered: false,
|
||||||
|
Votes: 0,
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,6 +34,34 @@ func (keys PublicKeys) Less(i, j int) bool {
|
||||||
return keys[i].Y.Cmp(keys[j].Y) == -1
|
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
|
// PublicKey represents a public key and provides a high level
|
||||||
// API around the X/Y point.
|
// API around the X/Y point.
|
||||||
type PublicKey struct {
|
type PublicKey struct {
|
||||||
|
@ -41,6 +69,11 @@ type PublicKey struct {
|
||||||
Y *big.Int
|
Y *big.Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Equal returns true in case public keys are equal.
|
||||||
|
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
|
// NewPublicKeyFromString returns a public key created from the
|
||||||
// given hex string.
|
// given hex string.
|
||||||
func NewPublicKeyFromString(s string) (*PublicKey, error) {
|
func NewPublicKeyFromString(s string) (*PublicKey, error) {
|
||||||
|
|
|
@ -5,49 +5,76 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/io"
|
"github.com/CityOfZion/neo-go/pkg/io"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEncodeDecodeInfinity(t *testing.T) {
|
func TestEncodeDecodeInfinity(t *testing.T) {
|
||||||
key := &PublicKey{}
|
key := &PublicKey{}
|
||||||
buf := io.NewBufBinWriter()
|
buf := io.NewBufBinWriter()
|
||||||
key.EncodeBinary(buf.BinWriter)
|
key.EncodeBinary(buf.BinWriter)
|
||||||
assert.Nil(t, buf.Err)
|
require.NoError(t, buf.Err)
|
||||||
b := buf.Bytes()
|
b := buf.Bytes()
|
||||||
assert.Equal(t, 1, len(b))
|
require.Equal(t, 1, len(b))
|
||||||
|
|
||||||
keyDecode := &PublicKey{}
|
keyDecode := &PublicKey{}
|
||||||
assert.Nil(t, keyDecode.DecodeBytes(b))
|
require.NoError(t, keyDecode.DecodeBytes(b))
|
||||||
assert.Equal(t, []byte{0x00}, keyDecode.Bytes())
|
require.Equal(t, []byte{0x00}, keyDecode.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEncodeDecodePublicKey(t *testing.T) {
|
func TestEncodeDecodePublicKey(t *testing.T) {
|
||||||
for i := 0; i < 4; i++ {
|
for i := 0; i < 4; i++ {
|
||||||
k, err := NewPrivateKey()
|
k, err := NewPrivateKey()
|
||||||
assert.Nil(t, err)
|
require.NoError(t, err)
|
||||||
p := k.PublicKey()
|
p := k.PublicKey()
|
||||||
buf := io.NewBufBinWriter()
|
buf := io.NewBufBinWriter()
|
||||||
p.EncodeBinary(buf.BinWriter)
|
p.EncodeBinary(buf.BinWriter)
|
||||||
assert.Nil(t, buf.Err)
|
require.NoError(t, buf.Err)
|
||||||
b := buf.Bytes()
|
b := buf.Bytes()
|
||||||
|
|
||||||
pDecode := &PublicKey{}
|
pDecode := &PublicKey{}
|
||||||
assert.Nil(t, pDecode.DecodeBytes(b))
|
require.NoError(t, pDecode.DecodeBytes(b))
|
||||||
assert.Equal(t, p.X, pDecode.X)
|
require.Equal(t, p.X, pDecode.X)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDecodeFromString(t *testing.T) {
|
func TestDecodeFromString(t *testing.T) {
|
||||||
str := "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"
|
str := "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"
|
||||||
pubKey, err := NewPublicKeyFromString(str)
|
pubKey, err := NewPublicKeyFromString(str)
|
||||||
assert.Nil(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, str, hex.EncodeToString(pubKey.Bytes()))
|
require.Equal(t, str, hex.EncodeToString(pubKey.Bytes()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPubkeyToAddress(t *testing.T) {
|
func TestPubkeyToAddress(t *testing.T) {
|
||||||
pubKey, err := NewPublicKeyFromString("031ee4e73a17d8f76dc02532e2620bcb12425b33c0c9f9694cc2caa8226b68cad4")
|
pubKey, err := NewPublicKeyFromString("031ee4e73a17d8f76dc02532e2620bcb12425b33c0c9f9694cc2caa8226b68cad4")
|
||||||
assert.Nil(t, err)
|
require.NoError(t, err)
|
||||||
actual := pubKey.Address()
|
actual := pubKey.Address()
|
||||||
expected := "AUpGsNCHzSimeMRVPQfhwrVdiUp8Q2N2Qx"
|
expected := "AUpGsNCHzSimeMRVPQfhwrVdiUp8Q2N2Qx"
|
||||||
assert.Equal(t, expected, actual)
|
require.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeBytes(t *testing.T) {
|
||||||
|
pubKey := getPubKey(t)
|
||||||
|
decodedPubKey := &PublicKey{}
|
||||||
|
err := decodedPubKey.DecodeBytes(pubKey.Bytes())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, pubKey,decodedPubKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContains(t *testing.T) {
|
||||||
|
pubKey := getPubKey(t)
|
||||||
|
pubKeys := &PublicKeys{getPubKey(t)}
|
||||||
|
pubKeys.Contains(pubKey)
|
||||||
|
require.True(t, pubKeys.Contains(pubKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnique(t *testing.T) {
|
||||||
|
pubKeys := &PublicKeys{getPubKey(t), getPubKey(t)}
|
||||||
|
unique := pubKeys.Unique()
|
||||||
|
require.Equal(t, 1, unique.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPubKey(t *testing.T) *PublicKey {
|
||||||
|
pubKey, err := NewPublicKeyFromString("031ee4e73a17d8f76dc02532e2620bcb12425b33c0c9f9694cc2caa8226b68cad4")
|
||||||
|
require.NoError(t, err)
|
||||||
|
return pubKey
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/CityOfZion/neo-go/pkg/core"
|
"github.com/CityOfZion/neo-go/pkg/core"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
"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/network/payload"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
"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 {
|
func (chain testChain) GetAccountState(util.Uint160) *core.AccountState {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
|
func (chain testChain) GetValidators(...*transaction.Transaction) ([]*keys.PublicKey, error) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
func (chain testChain) GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error) {
|
func (chain testChain) GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error) {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue