2020-03-25 10:00:11 +00:00
|
|
|
package native
|
|
|
|
|
|
|
|
import (
|
2022-05-04 14:00:18 +00:00
|
|
|
"context"
|
2020-07-13 09:59:41 +00:00
|
|
|
"crypto/elliptic"
|
2020-10-21 14:28:45 +00:00
|
|
|
"encoding/binary"
|
2020-08-06 14:44:08 +00:00
|
|
|
"errors"
|
2020-10-21 14:28:45 +00:00
|
|
|
"fmt"
|
2020-03-25 10:00:11 +00:00
|
|
|
"math/big"
|
|
|
|
"sort"
|
2020-05-29 08:02:26 +00:00
|
|
|
"strings"
|
2020-03-25 10:00:11 +00:00
|
|
|
|
2022-01-24 15:36:31 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
2020-03-25 10:00:11 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
|
2022-05-04 14:00:18 +00:00
|
|
|
istorage "github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
|
2020-12-13 18:25:04 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
2020-03-25 10:00:11 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
2020-11-06 09:27:05 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
2020-08-26 10:06:19 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
2020-03-25 10:00:11 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
2020-06-23 15:30:42 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
|
2022-04-27 19:58:52 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
2020-03-25 10:00:11 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
2020-12-29 10:45:49 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
2020-03-25 10:00:11 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
2022-04-27 19:58:52 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
2020-06-03 12:55:06 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
2020-03-25 10:00:11 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// NEO represents NEO native contract.
|
|
|
|
type NEO struct {
|
2020-11-19 15:01:42 +00:00
|
|
|
nep17TokenNative
|
2022-04-27 19:58:52 +00:00
|
|
|
GAS *GAS
|
|
|
|
Policy *Policy
|
2020-06-24 10:20:59 +00:00
|
|
|
|
2022-04-12 14:29:11 +00:00
|
|
|
// Configuration and standby keys are set in constructor and then
|
|
|
|
// only read from.
|
|
|
|
cfg config.ProtocolConfiguration
|
|
|
|
standbyKeys keys.PublicKeys
|
|
|
|
}
|
|
|
|
|
|
|
|
type NeoCache struct {
|
2022-04-19 16:40:27 +00:00
|
|
|
// gasPerBlock represents the history of generated gas per block.
|
|
|
|
gasPerBlock gasRecord
|
2020-09-28 07:51:25 +00:00
|
|
|
|
2022-04-19 16:46:50 +00:00
|
|
|
registerPrice int64
|
2021-03-05 11:17:58 +00:00
|
|
|
|
2022-04-19 09:26:46 +00:00
|
|
|
votesChanged bool
|
|
|
|
nextValidators keys.PublicKeys
|
2023-09-01 12:33:01 +00:00
|
|
|
// newEpochNextValidators contains cached next block newEpochNextValidators. This list is updated once
|
2023-09-01 12:10:42 +00:00
|
|
|
// per dBFT epoch in PostPersist of the last block in the epoch if candidates
|
|
|
|
// votes ratio has been changed or register/unregister operation was performed
|
|
|
|
// within the last processed epoch. The updated value is being persisted
|
|
|
|
// following the standard layered DAO persist rules, so that external users
|
|
|
|
// will get the proper value with upper Blockchain's DAO (but this value is
|
|
|
|
// relevant only by the moment of first epoch block creation).
|
2023-09-01 12:33:01 +00:00
|
|
|
newEpochNextValidators keys.PublicKeys
|
2020-11-05 07:43:43 +00:00
|
|
|
// committee contains cached committee members and their votes.
|
|
|
|
// It is updated once in a while depending on committee size
|
2020-09-22 10:03:34 +00:00
|
|
|
// (every 28 blocks for mainnet). It's value
|
2022-04-20 18:30:09 +00:00
|
|
|
// is always equal to the value stored by `prefixCommittee`.
|
2022-04-19 09:26:46 +00:00
|
|
|
committee keysWithVotes
|
2023-09-01 13:16:05 +00:00
|
|
|
// newEpochCommittee contains cached committee members updated once per dBFT
|
|
|
|
// epoch in PostPersist of the last block in the epoch.
|
|
|
|
newEpochCommittee keysWithVotes
|
2022-04-20 18:30:09 +00:00
|
|
|
// committeeHash contains the script hash of the committee.
|
2022-04-19 09:26:46 +00:00
|
|
|
committeeHash util.Uint160
|
2023-09-01 13:16:05 +00:00
|
|
|
// newEpochCommitteeHash contains the script hash of the newEpochCommittee.
|
|
|
|
newEpochCommitteeHash util.Uint160
|
2021-07-01 07:33:04 +00:00
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// gasPerVoteCache contains the last updated value of GAS per vote reward for candidates.
|
|
|
|
// It is set in state-modifying methods only and read in `PostPersist`, thus is not protected
|
2021-07-01 07:33:04 +00:00
|
|
|
// by any mutex.
|
|
|
|
gasPerVoteCache map[string]big.Int
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
2020-04-25 21:23:30 +00:00
|
|
|
const (
|
2021-02-15 15:43:10 +00:00
|
|
|
neoContractID = -5
|
2020-04-25 21:23:30 +00:00
|
|
|
// NEOTotalSupply is the total amount of NEO in the system.
|
|
|
|
NEOTotalSupply = 100000000
|
2022-04-20 18:30:09 +00:00
|
|
|
// DefaultRegisterPrice is the default price for candidate register.
|
2021-03-05 11:17:58 +00:00
|
|
|
DefaultRegisterPrice = 1000 * GASFactor
|
2020-08-03 08:43:51 +00:00
|
|
|
// prefixCandidate is a prefix used to store validator's data.
|
|
|
|
prefixCandidate = 33
|
2020-08-03 12:00:27 +00:00
|
|
|
// prefixVotersCount is a prefix for storing total amount of NEO of voters.
|
|
|
|
prefixVotersCount = 1
|
2020-11-03 15:08:58 +00:00
|
|
|
// prefixVoterRewardPerCommittee is a prefix for storing committee GAS reward.
|
|
|
|
prefixVoterRewardPerCommittee = 23
|
|
|
|
// voterRewardFactor is a factor by which voter reward per committee is multiplied
|
|
|
|
// to make calculations more precise.
|
|
|
|
voterRewardFactor = 100_000_000
|
2021-03-05 10:51:11 +00:00
|
|
|
// prefixGASPerBlock is a prefix for storing amount of GAS generated per block.
|
2020-08-26 09:07:30 +00:00
|
|
|
prefixGASPerBlock = 29
|
2021-03-05 11:17:58 +00:00
|
|
|
// prefixRegisterPrice is a prefix for storing candidate register price.
|
|
|
|
prefixRegisterPrice = 13
|
2020-08-03 12:00:27 +00:00
|
|
|
// effectiveVoterTurnout represents minimal ratio of total supply to total amount voted value
|
|
|
|
// which is require to use non-standby validators.
|
|
|
|
effectiveVoterTurnout = 5
|
2020-08-26 09:07:30 +00:00
|
|
|
// neoHolderRewardRatio is a percent of generated GAS that is distributed to NEO holders.
|
|
|
|
neoHolderRewardRatio = 10
|
|
|
|
// neoHolderRewardRatio is a percent of generated GAS that is distributed to committee.
|
2020-11-06 07:50:45 +00:00
|
|
|
committeeRewardRatio = 10
|
2020-08-26 09:07:30 +00:00
|
|
|
// neoHolderRewardRatio is a percent of generated GAS that is distributed to voters.
|
2020-11-06 07:50:45 +00:00
|
|
|
voterRewardRatio = 80
|
2022-05-04 14:00:18 +00:00
|
|
|
|
|
|
|
// maxGetCandidatesRespLen is the maximum number of candidates to return from the
|
|
|
|
// getCandidates method.
|
|
|
|
maxGetCandidatesRespLen = 256
|
2020-04-25 21:23:30 +00:00
|
|
|
)
|
|
|
|
|
2020-04-26 07:15:59 +00:00
|
|
|
var (
|
2020-08-28 07:24:54 +00:00
|
|
|
// prefixCommittee is a key used to store committee.
|
|
|
|
prefixCommittee = []byte{14}
|
2021-11-30 18:10:48 +00:00
|
|
|
|
|
|
|
bigCommitteeRewardRatio = big.NewInt(committeeRewardRatio)
|
|
|
|
bigVoterRewardRatio = big.NewInt(voterRewardRatio)
|
|
|
|
bigVoterRewardFactor = big.NewInt(voterRewardFactor)
|
|
|
|
bigEffectiveVoterTurnout = big.NewInt(effectiveVoterTurnout)
|
|
|
|
big100 = big.NewInt(100)
|
2020-04-26 07:15:59 +00:00
|
|
|
)
|
|
|
|
|
2022-04-15 14:48:58 +00:00
|
|
|
var (
|
2022-04-20 14:47:48 +00:00
|
|
|
_ interop.Contract = (*NEO)(nil)
|
|
|
|
_ dao.NativeContractCache = (*NeoCache)(nil)
|
2022-04-15 14:48:58 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Copy implements NativeContractCache interface.
|
2022-04-20 14:47:48 +00:00
|
|
|
func (c *NeoCache) Copy() dao.NativeContractCache {
|
2022-04-15 14:48:58 +00:00
|
|
|
cp := &NeoCache{}
|
|
|
|
copyNeoCache(c, cp)
|
|
|
|
return cp
|
|
|
|
}
|
|
|
|
|
|
|
|
func copyNeoCache(src, dst *NeoCache) {
|
2022-04-19 09:26:46 +00:00
|
|
|
dst.votesChanged = src.votesChanged
|
2022-04-28 15:38:13 +00:00
|
|
|
// Can safely omit copying because the new array is created each time
|
2023-09-01 12:33:01 +00:00
|
|
|
// newEpochNextValidators list, nextValidators and committee are updated.
|
2022-04-28 15:38:13 +00:00
|
|
|
dst.nextValidators = src.nextValidators
|
|
|
|
dst.committee = src.committee
|
2022-04-19 09:26:46 +00:00
|
|
|
dst.committeeHash = src.committeeHash
|
2023-09-01 15:15:36 +00:00
|
|
|
dst.newEpochNextValidators = src.newEpochNextValidators
|
|
|
|
dst.newEpochCommittee = src.newEpochCommittee
|
|
|
|
dst.newEpochCommitteeHash = src.newEpochCommitteeHash
|
2022-04-19 09:26:46 +00:00
|
|
|
|
|
|
|
dst.registerPrice = src.registerPrice
|
|
|
|
|
2022-04-19 16:40:27 +00:00
|
|
|
// Can't omit copying because gasPerBlock is append-only, thus to be able to
|
|
|
|
// discard cache changes in case of FAULTed transaction we need a separate
|
|
|
|
// container for updated gasPerBlock values.
|
2022-04-19 09:26:46 +00:00
|
|
|
dst.gasPerBlock = make(gasRecord, len(src.gasPerBlock))
|
|
|
|
copy(dst.gasPerBlock, src.gasPerBlock)
|
|
|
|
|
2022-04-15 14:48:58 +00:00
|
|
|
dst.gasPerVoteCache = make(map[string]big.Int)
|
|
|
|
for k, v := range src.gasPerVoteCache {
|
|
|
|
dst.gasPerVoteCache[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// makeValidatorKey creates a key from the account script hash.
|
2020-04-25 21:23:30 +00:00
|
|
|
func makeValidatorKey(key *keys.PublicKey) []byte {
|
|
|
|
b := key.Bytes()
|
|
|
|
// Don't create a new buffer.
|
|
|
|
b = append(b, 0)
|
|
|
|
copy(b[1:], b[0:])
|
2020-08-03 08:43:51 +00:00
|
|
|
b[0] = prefixCandidate
|
2020-04-25 21:23:30 +00:00
|
|
|
return b
|
|
|
|
}
|
2020-04-22 20:00:18 +00:00
|
|
|
|
2020-10-02 10:50:56 +00:00
|
|
|
// newNEO returns NEO native contract.
|
2022-04-13 13:09:39 +00:00
|
|
|
func newNEO(cfg config.ProtocolConfiguration) *NEO {
|
2020-05-14 18:01:56 +00:00
|
|
|
n := &NEO{}
|
2021-02-15 13:40:44 +00:00
|
|
|
defer n.UpdateHash()
|
|
|
|
|
2021-01-15 21:17:31 +00:00
|
|
|
nep17 := newNEP17Native(nativenames.Neo, neoContractID)
|
2020-12-13 18:05:49 +00:00
|
|
|
nep17.symbol = "NEO"
|
2020-11-19 15:01:42 +00:00
|
|
|
nep17.decimals = 0
|
|
|
|
nep17.factor = 1
|
|
|
|
nep17.incBalance = n.increaseBalance
|
2021-04-01 15:16:43 +00:00
|
|
|
nep17.balFromBytes = n.balanceFromBytes
|
2020-11-19 15:01:42 +00:00
|
|
|
|
|
|
|
n.nep17TokenNative = *nep17
|
2020-03-25 10:00:11 +00:00
|
|
|
|
2022-04-13 13:09:39 +00:00
|
|
|
err := n.initConfigCache(cfg)
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Errorf("failed to initialize NEO config cache: %w", err))
|
|
|
|
}
|
|
|
|
|
2020-03-25 10:00:11 +00:00
|
|
|
desc := newDescriptor("unclaimedGas", smartcontract.IntegerType,
|
2021-01-12 15:06:27 +00:00
|
|
|
manifest.NewParameter("account", smartcontract.Hash160Type),
|
2020-03-25 10:00:11 +00:00
|
|
|
manifest.NewParameter("end", smartcontract.IntegerType))
|
2021-03-05 10:30:16 +00:00
|
|
|
md := newMethodAndPrice(n.unclaimedGas, 1<<17, callflag.ReadStates)
|
2020-12-08 10:27:41 +00:00
|
|
|
n.AddMethod(md, desc)
|
2020-03-25 10:00:11 +00:00
|
|
|
|
2020-08-03 08:43:51 +00:00
|
|
|
desc = newDescriptor("registerCandidate", smartcontract.BoolType,
|
2021-03-05 12:44:22 +00:00
|
|
|
manifest.NewParameter("pubkey", smartcontract.PublicKeyType))
|
2021-03-05 10:30:16 +00:00
|
|
|
md = newMethodAndPrice(n.registerCandidate, 0, callflag.States)
|
2020-12-08 10:27:41 +00:00
|
|
|
n.AddMethod(md, desc)
|
2020-03-25 10:00:11 +00:00
|
|
|
|
2020-08-06 11:57:10 +00:00
|
|
|
desc = newDescriptor("unregisterCandidate", smartcontract.BoolType,
|
2021-03-05 12:44:22 +00:00
|
|
|
manifest.NewParameter("pubkey", smartcontract.PublicKeyType))
|
2021-03-05 10:30:16 +00:00
|
|
|
md = newMethodAndPrice(n.unregisterCandidate, 1<<16, callflag.States)
|
2020-12-08 10:27:41 +00:00
|
|
|
n.AddMethod(md, desc)
|
2020-08-06 11:57:10 +00:00
|
|
|
|
2020-03-25 10:00:11 +00:00
|
|
|
desc = newDescriptor("vote", smartcontract.BoolType,
|
2021-01-12 15:06:27 +00:00
|
|
|
manifest.NewParameter("account", smartcontract.Hash160Type),
|
2021-03-05 12:44:22 +00:00
|
|
|
manifest.NewParameter("voteTo", smartcontract.PublicKeyType))
|
2021-03-05 10:30:16 +00:00
|
|
|
md = newMethodAndPrice(n.vote, 1<<16, callflag.States)
|
2020-12-08 10:27:41 +00:00
|
|
|
n.AddMethod(md, desc)
|
2020-03-25 10:00:11 +00:00
|
|
|
|
2020-08-03 08:43:51 +00:00
|
|
|
desc = newDescriptor("getCandidates", smartcontract.ArrayType)
|
2021-03-05 10:30:16 +00:00
|
|
|
md = newMethodAndPrice(n.getCandidatesCall, 1<<22, callflag.ReadStates)
|
2020-12-08 10:27:41 +00:00
|
|
|
n.AddMethod(md, desc)
|
2020-03-25 10:00:11 +00:00
|
|
|
|
2022-05-04 14:00:18 +00:00
|
|
|
desc = newDescriptor("getAllCandidates", smartcontract.InteropInterfaceType)
|
|
|
|
md = newMethodAndPrice(n.getAllCandidatesCall, 1<<22, callflag.ReadStates)
|
|
|
|
n.AddMethod(md, desc)
|
|
|
|
|
|
|
|
desc = newDescriptor("getCandidateVote", smartcontract.IntegerType,
|
2022-06-02 05:00:37 +00:00
|
|
|
manifest.NewParameter("pubKey", smartcontract.PublicKeyType))
|
2022-05-04 14:00:18 +00:00
|
|
|
md = newMethodAndPrice(n.getCandidateVoteCall, 1<<15, callflag.ReadStates)
|
|
|
|
n.AddMethod(md, desc)
|
|
|
|
|
2021-05-25 14:54:57 +00:00
|
|
|
desc = newDescriptor("getAccountState", smartcontract.ArrayType,
|
|
|
|
manifest.NewParameter("account", smartcontract.Hash160Type))
|
|
|
|
md = newMethodAndPrice(n.getAccountState, 1<<15, callflag.ReadStates)
|
|
|
|
n.AddMethod(md, desc)
|
|
|
|
|
2021-02-05 09:09:15 +00:00
|
|
|
desc = newDescriptor("getCommittee", smartcontract.ArrayType)
|
2021-03-12 08:32:27 +00:00
|
|
|
md = newMethodAndPrice(n.getCommittee, 1<<16, callflag.ReadStates)
|
2020-12-08 10:27:41 +00:00
|
|
|
n.AddMethod(md, desc)
|
2020-08-03 12:00:27 +00:00
|
|
|
|
2020-03-25 10:00:11 +00:00
|
|
|
desc = newDescriptor("getNextBlockValidators", smartcontract.ArrayType)
|
2021-03-12 08:32:27 +00:00
|
|
|
md = newMethodAndPrice(n.getNextBlockValidators, 1<<16, callflag.ReadStates)
|
2020-12-08 10:27:41 +00:00
|
|
|
n.AddMethod(md, desc)
|
2020-03-25 10:00:11 +00:00
|
|
|
|
2020-08-26 10:06:19 +00:00
|
|
|
desc = newDescriptor("getGasPerBlock", smartcontract.IntegerType)
|
2021-03-05 10:30:16 +00:00
|
|
|
md = newMethodAndPrice(n.getGASPerBlock, 1<<15, callflag.ReadStates)
|
2020-12-08 10:27:41 +00:00
|
|
|
n.AddMethod(md, desc)
|
2020-08-26 10:06:19 +00:00
|
|
|
|
2021-01-28 15:01:30 +00:00
|
|
|
desc = newDescriptor("setGasPerBlock", smartcontract.VoidType,
|
2020-08-26 10:06:19 +00:00
|
|
|
manifest.NewParameter("gasPerBlock", smartcontract.IntegerType))
|
2021-03-05 10:30:16 +00:00
|
|
|
md = newMethodAndPrice(n.setGASPerBlock, 1<<15, callflag.States)
|
2020-12-08 10:27:41 +00:00
|
|
|
n.AddMethod(md, desc)
|
2020-08-26 10:06:19 +00:00
|
|
|
|
2021-03-05 11:17:58 +00:00
|
|
|
desc = newDescriptor("getRegisterPrice", smartcontract.IntegerType)
|
|
|
|
md = newMethodAndPrice(n.getRegisterPrice, 1<<15, callflag.ReadStates)
|
|
|
|
n.AddMethod(md, desc)
|
|
|
|
|
|
|
|
desc = newDescriptor("setRegisterPrice", smartcontract.VoidType,
|
|
|
|
manifest.NewParameter("registerPrice", smartcontract.IntegerType))
|
|
|
|
md = newMethodAndPrice(n.setRegisterPrice, 1<<15, callflag.States)
|
|
|
|
n.AddMethod(md, desc)
|
|
|
|
|
2022-05-27 12:33:17 +00:00
|
|
|
n.AddEvent("CandidateStateChanged",
|
|
|
|
manifest.NewParameter("pubkey", smartcontract.PublicKeyType),
|
|
|
|
manifest.NewParameter("registered", smartcontract.BoolType),
|
|
|
|
manifest.NewParameter("votes", smartcontract.IntegerType),
|
|
|
|
)
|
|
|
|
n.AddEvent("Vote",
|
|
|
|
manifest.NewParameter("account", smartcontract.Hash160Type),
|
|
|
|
manifest.NewParameter("from", smartcontract.PublicKeyType),
|
|
|
|
manifest.NewParameter("to", smartcontract.PublicKeyType),
|
|
|
|
manifest.NewParameter("amount", smartcontract.IntegerType),
|
|
|
|
)
|
2024-03-15 11:46:50 +00:00
|
|
|
n.AddEvent("CommitteeChanged",
|
|
|
|
manifest.NewParameter("old", smartcontract.ArrayType),
|
|
|
|
manifest.NewParameter("new", smartcontract.ArrayType),
|
|
|
|
)
|
2022-05-27 12:33:17 +00:00
|
|
|
|
2020-03-25 10:00:11 +00:00
|
|
|
return n
|
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// Initialize initializes a NEO contract.
|
2020-03-25 10:00:11 +00:00
|
|
|
func (n *NEO) Initialize(ic *interop.Context) error {
|
2020-11-19 15:01:42 +00:00
|
|
|
if err := n.nep17TokenNative.Initialize(ic); err != nil {
|
2020-03-25 10:00:11 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-08-02 21:19:23 +00:00
|
|
|
_, totalSupply := n.nep17TokenNative.getTotalSupply(ic.DAO)
|
|
|
|
if totalSupply.Sign() != 0 {
|
2020-04-22 20:00:18 +00:00
|
|
|
return errors.New("already initialized")
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
2022-04-12 14:29:11 +00:00
|
|
|
cache := &NeoCache{
|
2023-09-01 13:16:05 +00:00
|
|
|
gasPerVoteCache: make(map[string]big.Int),
|
|
|
|
votesChanged: true,
|
2022-04-12 14:29:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// We need cache to be present in DAO before the subsequent call to `mint`.
|
2022-04-20 14:47:48 +00:00
|
|
|
ic.DAO.SetCache(n.ID, cache)
|
2022-04-12 14:29:11 +00:00
|
|
|
|
2022-01-24 15:36:31 +00:00
|
|
|
committee0 := n.standbyKeys[:n.cfg.GetCommitteeSize(ic.Block.Index)]
|
|
|
|
cvs := toKeysWithVotes(committee0)
|
2022-04-29 15:00:46 +00:00
|
|
|
err := n.updateCache(cache, cvs, ic.BlockHeight())
|
2020-09-24 12:36:14 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-08-28 07:24:54 +00:00
|
|
|
|
2022-05-31 17:10:20 +00:00
|
|
|
ic.DAO.PutStorageItem(n.ID, prefixCommittee, cvs.Bytes(ic.DAO.GetItemCtx()))
|
2020-08-28 07:24:54 +00:00
|
|
|
|
2020-08-10 14:51:46 +00:00
|
|
|
h, err := getStandbyValidatorsHash(ic)
|
2020-03-25 10:00:11 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-11-30 10:05:42 +00:00
|
|
|
n.mint(ic, h, big.NewInt(NEOTotalSupply), false)
|
2020-03-25 10:00:11 +00:00
|
|
|
|
2021-09-14 11:39:39 +00:00
|
|
|
var index uint32
|
2020-10-21 14:28:45 +00:00
|
|
|
value := big.NewInt(5 * GASFactor)
|
2022-02-16 14:48:15 +00:00
|
|
|
n.putGASRecord(ic.DAO, index, value)
|
|
|
|
|
2020-10-21 14:28:45 +00:00
|
|
|
gr := &gasRecord{{Index: index, GASPerBlock: *value}}
|
2022-04-19 09:26:46 +00:00
|
|
|
cache.gasPerBlock = *gr
|
2022-02-16 14:48:15 +00:00
|
|
|
ic.DAO.PutStorageItem(n.ID, []byte{prefixVotersCount}, state.StorageItem{})
|
2020-08-03 12:00:27 +00:00
|
|
|
|
2022-02-16 14:48:15 +00:00
|
|
|
setIntWithKey(n.ID, ic.DAO, []byte{prefixRegisterPrice}, DefaultRegisterPrice)
|
2022-04-19 09:26:46 +00:00
|
|
|
cache.registerPrice = int64(DefaultRegisterPrice)
|
2022-04-12 14:29:11 +00:00
|
|
|
|
2023-09-01 15:15:36 +00:00
|
|
|
var numOfCNs = n.cfg.GetNumOfCNs(ic.Block.Index + 1)
|
|
|
|
err = n.updateCachedNewEpochValues(ic.DAO, cache, ic.BlockHeight(), numOfCNs)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to update next block newEpoch* cache: %w", err)
|
|
|
|
}
|
2020-03-25 10:00:11 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// InitializeCache initializes all NEO cache with the proper values from the storage.
|
|
|
|
// Cache initialization should be done apart from Initialize because Initialize is
|
2023-04-26 09:52:59 +00:00
|
|
|
// called only when deploying native contracts. InitializeCache implements the Contract
|
|
|
|
// interface.
|
2022-04-29 15:00:46 +00:00
|
|
|
func (n *NEO) InitializeCache(blockHeight uint32, d *dao.Simple) error {
|
2022-04-12 14:29:11 +00:00
|
|
|
cache := &NeoCache{
|
2022-04-19 16:46:50 +00:00
|
|
|
gasPerVoteCache: make(map[string]big.Int),
|
|
|
|
votesChanged: true,
|
2022-04-12 14:29:11 +00:00
|
|
|
}
|
|
|
|
|
2020-11-05 07:43:43 +00:00
|
|
|
var committee = keysWithVotes{}
|
2021-02-09 09:26:25 +00:00
|
|
|
si := d.GetStorageItem(n.ID, prefixCommittee)
|
2021-03-05 14:06:54 +00:00
|
|
|
if err := committee.DecodeBytes(si); err != nil {
|
2022-04-12 14:29:11 +00:00
|
|
|
return fmt.Errorf("failed to decode committee: %w", err)
|
core: add InitializeCache method to NEO native contracts
There might be a case when cached contract values store nil (e.g.
after restoring chain from dump). We should always initialize cached
values irrespective to the (NEO).Initialize method.
This commit fixes a bug introduced in 83e94d3
when 4-nodes privnet is failing after restoring from dump:
```
$ docker logs neo_go_node_one
=> Try to restore blocks before running node
2020-09-30T11:55:49.122Z INFO no storage version found! creating genesis block
2020-09-30T11:55:49.124Z INFO service hasn't started since it's disabled {"service": "Pprof"}
2020-09-30T11:55:49.124Z INFO service hasn't started since it's disabled {"service": "Prometheus"}
2020-09-30T11:55:49.124Z INFO skipped genesis block {"hash": "3792eaa22c196399a114666fd491c4b9ac52491d9abb1f633a8036a8ac81e4db"}
2020-09-30T11:55:49.141Z INFO shutting down service {"service": "Pprof", "endpoint": ":30001"}
2020-09-30T11:55:49.141Z INFO shutting down service {"service": "Prometheus", "endpoint": ":40001"}
2020-09-30T11:55:49.141Z INFO blockchain persist completed {"persistedBlocks": 3, "persistedKeys": 146, "headerHeight": 3, "blockHeight": 3, "took": "324.27µs"}
2020-09-30T11:55:49.150Z INFO restoring blockchain {"version": "0.1.0"}
2020-09-30T11:55:49.150Z INFO service hasn't started since it's disabled {"service": "Prometheus"}
2020-09-30T11:55:49.151Z INFO service hasn't started since it's disabled {"service": "Pprof"}
2020-09-30T11:55:49.443Z INFO starting rpc-server {"endpoint": ":30333"}
2020-09-30T11:55:49.443Z INFO node started {"blockHeight": 3, "headerHeight": 3}
_ ____________ __________
/ | / / ____/ __ \ / ____/ __ \
/ |/ / __/ / / / /_____/ / __/ / / /
/ /| / /___/ /_/ /_____/ /_/ / /_/ /
/_/ |_/_____/\____/ \____/\____/
/NEO-GO:/
2020-09-30T11:55:49.444Z INFO new peer connected {"addr": "172.23.0.5:39638", "peerCount": 1}
2020-09-30T11:55:49.444Z INFO new peer connected {"addr": "172.23.0.5:20333", "peerCount": 2}
2020-09-30T11:55:49.444Z WARN peer disconnected {"addr": "172.23.0.5:20333", "reason": "identical node id", "peerCount": 1}
2020-09-30T11:55:49.445Z WARN peer disconnected {"addr": "172.23.0.5:39638", "reason": "identical node id", "peerCount": 0}
2020-09-30T11:55:49.445Z INFO new peer connected {"addr": "172.23.0.3:20335", "peerCount": 1}
2020-09-30T11:55:49.445Z INFO new peer connected {"addr": "172.23.0.2:20334", "peerCount": 2}
2020-09-30T11:55:49.445Z INFO started protocol {"addr": "172.23.0.3:20335", "userAgent": "/NEO-GO:/", "startHeight": 3, "id": 1339919829}
2020-09-30T11:55:49.445Z INFO new peer connected {"addr": "172.23.0.4:20336", "peerCount": 3}
2020-09-30T11:55:49.445Z INFO started protocol {"addr": "172.23.0.4:20336", "userAgent": "/NEO-GO:/", "startHeight": 3, "id": 4036722359}
2020-09-30T11:55:49.445Z INFO node reached synchronized state, starting consensus
2020-09-30T11:55:49.445Z INFO started protocol {"addr": "172.23.0.2:20334", "userAgent": "/NEO-GO:/", "startHeight": 3, "id": 1557367037}
panic: runtime error: integer divide by zero
goroutine 132 [running]:
github.com/nspcc-dev/dbft.(*Context).GetPrimaryIndex(...)
github.com/nspcc-dev/dbft@v0.0.0-20200925163137-8f3b9ab3b720/context.go:83
github.com/nspcc-dev/dbft.(*Context).reset(0xc0000e0780, 0x0)
github.com/nspcc-dev/dbft@v0.0.0-20200925163137-8f3b9ab3b720/context.go:208 +0x64b
github.com/nspcc-dev/dbft.(*DBFT).InitializeConsensus(0xc0000e0780, 0x964800)
github.com/nspcc-dev/dbft@v0.0.0-20200925163137-8f3b9ab3b720/dbft.go:87 +0x51
github.com/nspcc-dev/dbft.(*DBFT).Start(0xc0000e0780)
github.com/nspcc-dev/dbft@v0.0.0-20200925163137-8f3b9ab3b720/dbft.go:81 +0x4b
github.com/nspcc-dev/neo-go/pkg/consensus.(*service).Start(0xc0001a2160)
github.com/nspcc-dev/neo-go/pkg/consensus/consensus.go:206 +0x56
github.com/nspcc-dev/neo-go/pkg/network.(*Server).tryStartConsensus(0xc0000ec500)
github.com/nspcc-dev/neo-go/pkg/network/server.go:311 +0xda
github.com/nspcc-dev/neo-go/pkg/network.(*Server).handleMessage(0xc0000ec500, 0x104d800, 0xc000222090, 0xc0000a6f10, 0x0, 0x0)
github.com/nspcc-dev/neo-go/pkg/network/server.go:781 +0xa7a
github.com/nspcc-dev/neo-go/pkg/network.(*TCPPeer).handleConn(0xc000222090)
github.com/nspcc-dev/neo-go/pkg/network/tcp_peer.go:162 +0x2e7
created by github.com/nspcc-dev/neo-go/pkg/network.(*TCPTransport).Dial
github.com/nspcc-dev/neo-go/pkg/network/tcp_transport.go:40 +0x1ac
```
2020-10-02 11:44:42 +00:00
|
|
|
}
|
2022-04-29 15:00:46 +00:00
|
|
|
if err := n.updateCache(cache, committee, blockHeight); err != nil {
|
2022-04-12 14:29:11 +00:00
|
|
|
return fmt.Errorf("failed to update cache: %w", err)
|
core: add InitializeCache method to NEO native contracts
There might be a case when cached contract values store nil (e.g.
after restoring chain from dump). We should always initialize cached
values irrespective to the (NEO).Initialize method.
This commit fixes a bug introduced in 83e94d3
when 4-nodes privnet is failing after restoring from dump:
```
$ docker logs neo_go_node_one
=> Try to restore blocks before running node
2020-09-30T11:55:49.122Z INFO no storage version found! creating genesis block
2020-09-30T11:55:49.124Z INFO service hasn't started since it's disabled {"service": "Pprof"}
2020-09-30T11:55:49.124Z INFO service hasn't started since it's disabled {"service": "Prometheus"}
2020-09-30T11:55:49.124Z INFO skipped genesis block {"hash": "3792eaa22c196399a114666fd491c4b9ac52491d9abb1f633a8036a8ac81e4db"}
2020-09-30T11:55:49.141Z INFO shutting down service {"service": "Pprof", "endpoint": ":30001"}
2020-09-30T11:55:49.141Z INFO shutting down service {"service": "Prometheus", "endpoint": ":40001"}
2020-09-30T11:55:49.141Z INFO blockchain persist completed {"persistedBlocks": 3, "persistedKeys": 146, "headerHeight": 3, "blockHeight": 3, "took": "324.27µs"}
2020-09-30T11:55:49.150Z INFO restoring blockchain {"version": "0.1.0"}
2020-09-30T11:55:49.150Z INFO service hasn't started since it's disabled {"service": "Prometheus"}
2020-09-30T11:55:49.151Z INFO service hasn't started since it's disabled {"service": "Pprof"}
2020-09-30T11:55:49.443Z INFO starting rpc-server {"endpoint": ":30333"}
2020-09-30T11:55:49.443Z INFO node started {"blockHeight": 3, "headerHeight": 3}
_ ____________ __________
/ | / / ____/ __ \ / ____/ __ \
/ |/ / __/ / / / /_____/ / __/ / / /
/ /| / /___/ /_/ /_____/ /_/ / /_/ /
/_/ |_/_____/\____/ \____/\____/
/NEO-GO:/
2020-09-30T11:55:49.444Z INFO new peer connected {"addr": "172.23.0.5:39638", "peerCount": 1}
2020-09-30T11:55:49.444Z INFO new peer connected {"addr": "172.23.0.5:20333", "peerCount": 2}
2020-09-30T11:55:49.444Z WARN peer disconnected {"addr": "172.23.0.5:20333", "reason": "identical node id", "peerCount": 1}
2020-09-30T11:55:49.445Z WARN peer disconnected {"addr": "172.23.0.5:39638", "reason": "identical node id", "peerCount": 0}
2020-09-30T11:55:49.445Z INFO new peer connected {"addr": "172.23.0.3:20335", "peerCount": 1}
2020-09-30T11:55:49.445Z INFO new peer connected {"addr": "172.23.0.2:20334", "peerCount": 2}
2020-09-30T11:55:49.445Z INFO started protocol {"addr": "172.23.0.3:20335", "userAgent": "/NEO-GO:/", "startHeight": 3, "id": 1339919829}
2020-09-30T11:55:49.445Z INFO new peer connected {"addr": "172.23.0.4:20336", "peerCount": 3}
2020-09-30T11:55:49.445Z INFO started protocol {"addr": "172.23.0.4:20336", "userAgent": "/NEO-GO:/", "startHeight": 3, "id": 4036722359}
2020-09-30T11:55:49.445Z INFO node reached synchronized state, starting consensus
2020-09-30T11:55:49.445Z INFO started protocol {"addr": "172.23.0.2:20334", "userAgent": "/NEO-GO:/", "startHeight": 3, "id": 1557367037}
panic: runtime error: integer divide by zero
goroutine 132 [running]:
github.com/nspcc-dev/dbft.(*Context).GetPrimaryIndex(...)
github.com/nspcc-dev/dbft@v0.0.0-20200925163137-8f3b9ab3b720/context.go:83
github.com/nspcc-dev/dbft.(*Context).reset(0xc0000e0780, 0x0)
github.com/nspcc-dev/dbft@v0.0.0-20200925163137-8f3b9ab3b720/context.go:208 +0x64b
github.com/nspcc-dev/dbft.(*DBFT).InitializeConsensus(0xc0000e0780, 0x964800)
github.com/nspcc-dev/dbft@v0.0.0-20200925163137-8f3b9ab3b720/dbft.go:87 +0x51
github.com/nspcc-dev/dbft.(*DBFT).Start(0xc0000e0780)
github.com/nspcc-dev/dbft@v0.0.0-20200925163137-8f3b9ab3b720/dbft.go:81 +0x4b
github.com/nspcc-dev/neo-go/pkg/consensus.(*service).Start(0xc0001a2160)
github.com/nspcc-dev/neo-go/pkg/consensus/consensus.go:206 +0x56
github.com/nspcc-dev/neo-go/pkg/network.(*Server).tryStartConsensus(0xc0000ec500)
github.com/nspcc-dev/neo-go/pkg/network/server.go:311 +0xda
github.com/nspcc-dev/neo-go/pkg/network.(*Server).handleMessage(0xc0000ec500, 0x104d800, 0xc000222090, 0xc0000a6f10, 0x0, 0x0)
github.com/nspcc-dev/neo-go/pkg/network/server.go:781 +0xa7a
github.com/nspcc-dev/neo-go/pkg/network.(*TCPPeer).handleConn(0xc000222090)
github.com/nspcc-dev/neo-go/pkg/network/tcp_peer.go:162 +0x2e7
created by github.com/nspcc-dev/neo-go/pkg/network.(*TCPTransport).Dial
github.com/nspcc-dev/neo-go/pkg/network/tcp_transport.go:40 +0x1ac
```
2020-10-02 11:44:42 +00:00
|
|
|
}
|
|
|
|
|
2022-04-19 09:26:46 +00:00
|
|
|
cache.gasPerBlock = n.getSortedGASRecordFromDAO(d)
|
2022-04-19 16:46:50 +00:00
|
|
|
cache.registerPrice = getIntWithKey(n.ID, d, []byte{prefixRegisterPrice})
|
core: add InitializeCache method to NEO native contracts
There might be a case when cached contract values store nil (e.g.
after restoring chain from dump). We should always initialize cached
values irrespective to the (NEO).Initialize method.
This commit fixes a bug introduced in 83e94d3
when 4-nodes privnet is failing after restoring from dump:
```
$ docker logs neo_go_node_one
=> Try to restore blocks before running node
2020-09-30T11:55:49.122Z INFO no storage version found! creating genesis block
2020-09-30T11:55:49.124Z INFO service hasn't started since it's disabled {"service": "Pprof"}
2020-09-30T11:55:49.124Z INFO service hasn't started since it's disabled {"service": "Prometheus"}
2020-09-30T11:55:49.124Z INFO skipped genesis block {"hash": "3792eaa22c196399a114666fd491c4b9ac52491d9abb1f633a8036a8ac81e4db"}
2020-09-30T11:55:49.141Z INFO shutting down service {"service": "Pprof", "endpoint": ":30001"}
2020-09-30T11:55:49.141Z INFO shutting down service {"service": "Prometheus", "endpoint": ":40001"}
2020-09-30T11:55:49.141Z INFO blockchain persist completed {"persistedBlocks": 3, "persistedKeys": 146, "headerHeight": 3, "blockHeight": 3, "took": "324.27µs"}
2020-09-30T11:55:49.150Z INFO restoring blockchain {"version": "0.1.0"}
2020-09-30T11:55:49.150Z INFO service hasn't started since it's disabled {"service": "Prometheus"}
2020-09-30T11:55:49.151Z INFO service hasn't started since it's disabled {"service": "Pprof"}
2020-09-30T11:55:49.443Z INFO starting rpc-server {"endpoint": ":30333"}
2020-09-30T11:55:49.443Z INFO node started {"blockHeight": 3, "headerHeight": 3}
_ ____________ __________
/ | / / ____/ __ \ / ____/ __ \
/ |/ / __/ / / / /_____/ / __/ / / /
/ /| / /___/ /_/ /_____/ /_/ / /_/ /
/_/ |_/_____/\____/ \____/\____/
/NEO-GO:/
2020-09-30T11:55:49.444Z INFO new peer connected {"addr": "172.23.0.5:39638", "peerCount": 1}
2020-09-30T11:55:49.444Z INFO new peer connected {"addr": "172.23.0.5:20333", "peerCount": 2}
2020-09-30T11:55:49.444Z WARN peer disconnected {"addr": "172.23.0.5:20333", "reason": "identical node id", "peerCount": 1}
2020-09-30T11:55:49.445Z WARN peer disconnected {"addr": "172.23.0.5:39638", "reason": "identical node id", "peerCount": 0}
2020-09-30T11:55:49.445Z INFO new peer connected {"addr": "172.23.0.3:20335", "peerCount": 1}
2020-09-30T11:55:49.445Z INFO new peer connected {"addr": "172.23.0.2:20334", "peerCount": 2}
2020-09-30T11:55:49.445Z INFO started protocol {"addr": "172.23.0.3:20335", "userAgent": "/NEO-GO:/", "startHeight": 3, "id": 1339919829}
2020-09-30T11:55:49.445Z INFO new peer connected {"addr": "172.23.0.4:20336", "peerCount": 3}
2020-09-30T11:55:49.445Z INFO started protocol {"addr": "172.23.0.4:20336", "userAgent": "/NEO-GO:/", "startHeight": 3, "id": 4036722359}
2020-09-30T11:55:49.445Z INFO node reached synchronized state, starting consensus
2020-09-30T11:55:49.445Z INFO started protocol {"addr": "172.23.0.2:20334", "userAgent": "/NEO-GO:/", "startHeight": 3, "id": 1557367037}
panic: runtime error: integer divide by zero
goroutine 132 [running]:
github.com/nspcc-dev/dbft.(*Context).GetPrimaryIndex(...)
github.com/nspcc-dev/dbft@v0.0.0-20200925163137-8f3b9ab3b720/context.go:83
github.com/nspcc-dev/dbft.(*Context).reset(0xc0000e0780, 0x0)
github.com/nspcc-dev/dbft@v0.0.0-20200925163137-8f3b9ab3b720/context.go:208 +0x64b
github.com/nspcc-dev/dbft.(*DBFT).InitializeConsensus(0xc0000e0780, 0x964800)
github.com/nspcc-dev/dbft@v0.0.0-20200925163137-8f3b9ab3b720/dbft.go:87 +0x51
github.com/nspcc-dev/dbft.(*DBFT).Start(0xc0000e0780)
github.com/nspcc-dev/dbft@v0.0.0-20200925163137-8f3b9ab3b720/dbft.go:81 +0x4b
github.com/nspcc-dev/neo-go/pkg/consensus.(*service).Start(0xc0001a2160)
github.com/nspcc-dev/neo-go/pkg/consensus/consensus.go:206 +0x56
github.com/nspcc-dev/neo-go/pkg/network.(*Server).tryStartConsensus(0xc0000ec500)
github.com/nspcc-dev/neo-go/pkg/network/server.go:311 +0xda
github.com/nspcc-dev/neo-go/pkg/network.(*Server).handleMessage(0xc0000ec500, 0x104d800, 0xc000222090, 0xc0000a6f10, 0x0, 0x0)
github.com/nspcc-dev/neo-go/pkg/network/server.go:781 +0xa7a
github.com/nspcc-dev/neo-go/pkg/network.(*TCPPeer).handleConn(0xc000222090)
github.com/nspcc-dev/neo-go/pkg/network/tcp_peer.go:162 +0x2e7
created by github.com/nspcc-dev/neo-go/pkg/network.(*TCPTransport).Dial
github.com/nspcc-dev/neo-go/pkg/network/tcp_transport.go:40 +0x1ac
```
2020-10-02 11:44:42 +00:00
|
|
|
|
2023-09-01 15:15:36 +00:00
|
|
|
// Update newEpoch* cache for external users. It holds values for the previous
|
|
|
|
// dBFT epoch if the current one isn't yet finished.
|
2023-09-01 12:10:42 +00:00
|
|
|
if n.cfg.ShouldUpdateCommitteeAt(blockHeight + 1) {
|
|
|
|
var numOfCNs = n.cfg.GetNumOfCNs(blockHeight + 1)
|
2023-09-01 13:16:05 +00:00
|
|
|
err := n.updateCachedNewEpochValues(d, cache, blockHeight, numOfCNs)
|
2023-09-01 12:10:42 +00:00
|
|
|
if err != nil {
|
2023-09-01 13:16:05 +00:00
|
|
|
return fmt.Errorf("failed to update next block newEpoch* cache: %w", err)
|
2023-09-01 12:10:42 +00:00
|
|
|
}
|
2023-09-01 15:15:36 +00:00
|
|
|
} else {
|
|
|
|
// nextValidators, committee and committee hash are filled in by this moment
|
|
|
|
// via n.updateCache call.
|
|
|
|
cache.newEpochNextValidators = cache.nextValidators.Copy()
|
|
|
|
cache.newEpochCommittee = make(keysWithVotes, len(cache.committee))
|
|
|
|
copy(cache.newEpochCommittee, cache.committee)
|
|
|
|
cache.newEpochCommitteeHash = cache.committeeHash
|
native: rework native NEO next block validators cache
Blockchain passes his own pure unwrapped DAO to
(*Blockchain).ComputeNextBlockValidators which means that native
RW NEO cache structure stored inside this DAO can be modified by
anyone who uses exported ComputeNextBlockValidators Blockchain API,
and technically it's valid, and we should allow this, because it's
the only purpose of `validators` caching. However, at the same time
some RPC server is allowed to request a subsequent wrapped DAO for
some test invocation. It means that descendant wrapped DAO
eventually will request RW NEO cache and try to `Copy()`
the underlying's DAO cache which is in direct use of
ComputeNextBlockValidators. Here's the race:
ComputeNextBlockValidators called by Consensus service tries to
update cached `validators` value, and descendant wrapped DAO
created by the RPC server tries to copy DAO's native cache and
read the cached `validators` value.
So the problem is that native cache not designated to handle
concurrent access between parent DAO layer and derived (wrapped)
DAO layer. I've carefully reviewed all the usages of native cache,
and turns out that the described situation is the only place where
parent DAO is used directly to modify its cache concurrently with
some descendant DAO that is trying to access the cache. All other
usages of native cache (not only NEO, but also all other native
contrcts) strictly rely on the hierarchical DAO structure and don't
try to perform these concurrent operations between DAO layers.
There's also persist operation, but it keeps cache RW lock taken,
so it doesn't have this problem as far. Thus, in this commit we rework
NEO's `validators` cache value so that it always contain the relevant
list for upper Blockchain's DAO and is updated every PostPersist (if
needed).
Note: we must be very careful extending our native cache in the
future, every usage of native cache must be checked against the
described problem.
Close #2989.
Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
2023-08-30 11:02:42 +00:00
|
|
|
}
|
|
|
|
|
2022-04-20 14:47:48 +00:00
|
|
|
d.SetCache(n.ID, cache)
|
core: add InitializeCache method to NEO native contracts
There might be a case when cached contract values store nil (e.g.
after restoring chain from dump). We should always initialize cached
values irrespective to the (NEO).Initialize method.
This commit fixes a bug introduced in 83e94d3
when 4-nodes privnet is failing after restoring from dump:
```
$ docker logs neo_go_node_one
=> Try to restore blocks before running node
2020-09-30T11:55:49.122Z INFO no storage version found! creating genesis block
2020-09-30T11:55:49.124Z INFO service hasn't started since it's disabled {"service": "Pprof"}
2020-09-30T11:55:49.124Z INFO service hasn't started since it's disabled {"service": "Prometheus"}
2020-09-30T11:55:49.124Z INFO skipped genesis block {"hash": "3792eaa22c196399a114666fd491c4b9ac52491d9abb1f633a8036a8ac81e4db"}
2020-09-30T11:55:49.141Z INFO shutting down service {"service": "Pprof", "endpoint": ":30001"}
2020-09-30T11:55:49.141Z INFO shutting down service {"service": "Prometheus", "endpoint": ":40001"}
2020-09-30T11:55:49.141Z INFO blockchain persist completed {"persistedBlocks": 3, "persistedKeys": 146, "headerHeight": 3, "blockHeight": 3, "took": "324.27µs"}
2020-09-30T11:55:49.150Z INFO restoring blockchain {"version": "0.1.0"}
2020-09-30T11:55:49.150Z INFO service hasn't started since it's disabled {"service": "Prometheus"}
2020-09-30T11:55:49.151Z INFO service hasn't started since it's disabled {"service": "Pprof"}
2020-09-30T11:55:49.443Z INFO starting rpc-server {"endpoint": ":30333"}
2020-09-30T11:55:49.443Z INFO node started {"blockHeight": 3, "headerHeight": 3}
_ ____________ __________
/ | / / ____/ __ \ / ____/ __ \
/ |/ / __/ / / / /_____/ / __/ / / /
/ /| / /___/ /_/ /_____/ /_/ / /_/ /
/_/ |_/_____/\____/ \____/\____/
/NEO-GO:/
2020-09-30T11:55:49.444Z INFO new peer connected {"addr": "172.23.0.5:39638", "peerCount": 1}
2020-09-30T11:55:49.444Z INFO new peer connected {"addr": "172.23.0.5:20333", "peerCount": 2}
2020-09-30T11:55:49.444Z WARN peer disconnected {"addr": "172.23.0.5:20333", "reason": "identical node id", "peerCount": 1}
2020-09-30T11:55:49.445Z WARN peer disconnected {"addr": "172.23.0.5:39638", "reason": "identical node id", "peerCount": 0}
2020-09-30T11:55:49.445Z INFO new peer connected {"addr": "172.23.0.3:20335", "peerCount": 1}
2020-09-30T11:55:49.445Z INFO new peer connected {"addr": "172.23.0.2:20334", "peerCount": 2}
2020-09-30T11:55:49.445Z INFO started protocol {"addr": "172.23.0.3:20335", "userAgent": "/NEO-GO:/", "startHeight": 3, "id": 1339919829}
2020-09-30T11:55:49.445Z INFO new peer connected {"addr": "172.23.0.4:20336", "peerCount": 3}
2020-09-30T11:55:49.445Z INFO started protocol {"addr": "172.23.0.4:20336", "userAgent": "/NEO-GO:/", "startHeight": 3, "id": 4036722359}
2020-09-30T11:55:49.445Z INFO node reached synchronized state, starting consensus
2020-09-30T11:55:49.445Z INFO started protocol {"addr": "172.23.0.2:20334", "userAgent": "/NEO-GO:/", "startHeight": 3, "id": 1557367037}
panic: runtime error: integer divide by zero
goroutine 132 [running]:
github.com/nspcc-dev/dbft.(*Context).GetPrimaryIndex(...)
github.com/nspcc-dev/dbft@v0.0.0-20200925163137-8f3b9ab3b720/context.go:83
github.com/nspcc-dev/dbft.(*Context).reset(0xc0000e0780, 0x0)
github.com/nspcc-dev/dbft@v0.0.0-20200925163137-8f3b9ab3b720/context.go:208 +0x64b
github.com/nspcc-dev/dbft.(*DBFT).InitializeConsensus(0xc0000e0780, 0x964800)
github.com/nspcc-dev/dbft@v0.0.0-20200925163137-8f3b9ab3b720/dbft.go:87 +0x51
github.com/nspcc-dev/dbft.(*DBFT).Start(0xc0000e0780)
github.com/nspcc-dev/dbft@v0.0.0-20200925163137-8f3b9ab3b720/dbft.go:81 +0x4b
github.com/nspcc-dev/neo-go/pkg/consensus.(*service).Start(0xc0001a2160)
github.com/nspcc-dev/neo-go/pkg/consensus/consensus.go:206 +0x56
github.com/nspcc-dev/neo-go/pkg/network.(*Server).tryStartConsensus(0xc0000ec500)
github.com/nspcc-dev/neo-go/pkg/network/server.go:311 +0xda
github.com/nspcc-dev/neo-go/pkg/network.(*Server).handleMessage(0xc0000ec500, 0x104d800, 0xc000222090, 0xc0000a6f10, 0x0, 0x0)
github.com/nspcc-dev/neo-go/pkg/network/server.go:781 +0xa7a
github.com/nspcc-dev/neo-go/pkg/network.(*TCPPeer).handleConn(0xc000222090)
github.com/nspcc-dev/neo-go/pkg/network/tcp_peer.go:162 +0x2e7
created by github.com/nspcc-dev/neo-go/pkg/network.(*TCPTransport).Dial
github.com/nspcc-dev/neo-go/pkg/network/tcp_transport.go:40 +0x1ac
```
2020-10-02 11:44:42 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-11-21 09:35:18 +00:00
|
|
|
// ActiveIn implements the Contract interface.
|
|
|
|
func (n *NEO) ActiveIn() *config.Hardfork {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-04-13 13:09:39 +00:00
|
|
|
func (n *NEO) initConfigCache(cfg config.ProtocolConfiguration) error {
|
2022-01-24 15:36:31 +00:00
|
|
|
var err error
|
|
|
|
|
2022-04-13 13:09:39 +00:00
|
|
|
n.cfg = cfg
|
2022-01-24 15:36:31 +00:00
|
|
|
n.standbyKeys, err = keys.NewPublicKeysFromStrings(n.cfg.StandbyCommittee)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-04-29 15:00:46 +00:00
|
|
|
func (n *NEO) updateCache(cache *NeoCache, cvs keysWithVotes, blockHeight uint32) error {
|
2022-04-19 09:26:46 +00:00
|
|
|
cache.committee = cvs
|
2020-11-05 07:43:43 +00:00
|
|
|
|
2023-09-01 13:00:53 +00:00
|
|
|
var committee = getCommitteeMembers(cache.committee)
|
2020-09-28 12:29:43 +00:00
|
|
|
script, err := smartcontract.CreateMajorityMultiSigRedeemScript(committee.Copy())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-04-19 09:26:46 +00:00
|
|
|
cache.committeeHash = hash.Hash160(script)
|
2020-09-28 12:29:43 +00:00
|
|
|
|
2022-04-29 15:00:46 +00:00
|
|
|
nextVals := committee[:n.cfg.GetNumOfCNs(blockHeight+1)].Copy()
|
2020-08-28 07:24:54 +00:00
|
|
|
sort.Sort(nextVals)
|
2022-04-19 09:26:46 +00:00
|
|
|
cache.nextValidators = nextVals
|
2020-09-28 12:29:43 +00:00
|
|
|
return nil
|
2020-08-28 07:24:54 +00:00
|
|
|
}
|
|
|
|
|
2023-09-01 13:16:05 +00:00
|
|
|
// updateCachedNewEpochValues sets newEpochNextValidators, newEpochCommittee and
|
|
|
|
// newEpochCommitteeHash cache that will be used by external users to retrieve
|
|
|
|
// next block validators list of the next dBFT epoch that wasn't yet started and
|
|
|
|
// will be used by corresponding values initialisation on the next epoch start.
|
|
|
|
// The updated new epoch cached values computed using the persisted blocks state
|
|
|
|
// of the latest epoch.
|
|
|
|
func (n *NEO) updateCachedNewEpochValues(d *dao.Simple, cache *NeoCache, blockHeight uint32, numOfCNs int) error {
|
|
|
|
committee, cvs, err := n.computeCommitteeMembers(blockHeight, d)
|
native: rework native NEO next block validators cache
Blockchain passes his own pure unwrapped DAO to
(*Blockchain).ComputeNextBlockValidators which means that native
RW NEO cache structure stored inside this DAO can be modified by
anyone who uses exported ComputeNextBlockValidators Blockchain API,
and technically it's valid, and we should allow this, because it's
the only purpose of `validators` caching. However, at the same time
some RPC server is allowed to request a subsequent wrapped DAO for
some test invocation. It means that descendant wrapped DAO
eventually will request RW NEO cache and try to `Copy()`
the underlying's DAO cache which is in direct use of
ComputeNextBlockValidators. Here's the race:
ComputeNextBlockValidators called by Consensus service tries to
update cached `validators` value, and descendant wrapped DAO
created by the RPC server tries to copy DAO's native cache and
read the cached `validators` value.
So the problem is that native cache not designated to handle
concurrent access between parent DAO layer and derived (wrapped)
DAO layer. I've carefully reviewed all the usages of native cache,
and turns out that the described situation is the only place where
parent DAO is used directly to modify its cache concurrently with
some descendant DAO that is trying to access the cache. All other
usages of native cache (not only NEO, but also all other native
contrcts) strictly rely on the hierarchical DAO structure and don't
try to perform these concurrent operations between DAO layers.
There's also persist operation, but it keeps cache RW lock taken,
so it doesn't have this problem as far. Thus, in this commit we rework
NEO's `validators` cache value so that it always contain the relevant
list for upper Blockchain's DAO and is updated every PostPersist (if
needed).
Note: we must be very careful extending our native cache in the
future, every usage of native cache must be checked against the
described problem.
Close #2989.
Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
2023-08-30 11:02:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to compute committee members: %w", err)
|
|
|
|
}
|
2023-09-01 13:16:05 +00:00
|
|
|
cache.newEpochCommittee = cvs
|
2020-08-28 07:24:54 +00:00
|
|
|
|
2023-09-01 13:16:05 +00:00
|
|
|
script, err := smartcontract.CreateMajorityMultiSigRedeemScript(committee.Copy())
|
2020-08-28 07:24:54 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-09-01 13:16:05 +00:00
|
|
|
cache.newEpochCommitteeHash = hash.Hash160(script)
|
|
|
|
|
|
|
|
nextVals := committee[:numOfCNs].Copy()
|
|
|
|
sort.Sort(nextVals)
|
|
|
|
cache.newEpochNextValidators = nextVals
|
2022-02-16 14:48:15 +00:00
|
|
|
return nil
|
2020-08-28 07:24:54 +00:00
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// OnPersist implements the Contract interface.
|
2020-03-25 10:00:11 +00:00
|
|
|
func (n *NEO) OnPersist(ic *interop.Context) error {
|
2022-01-24 15:36:31 +00:00
|
|
|
if n.cfg.ShouldUpdateCommitteeAt(ic.Block.Index) {
|
2022-04-20 14:47:48 +00:00
|
|
|
cache := ic.DAO.GetRWCache(n.ID).(*NeoCache)
|
2023-09-01 13:16:05 +00:00
|
|
|
// Cached newEpoch* values always have proper value set (either by PostPersist
|
|
|
|
// during the last epoch block handling or by initialization code).
|
2024-03-15 11:46:50 +00:00
|
|
|
|
|
|
|
var oldCommittee, newCommittee stackitem.Item
|
|
|
|
for i := 0; i < len(cache.committee); i++ {
|
|
|
|
if cache.newEpochCommittee[i].Key != cache.committee[i].Key ||
|
|
|
|
(i == 0 && len(cache.newEpochCommittee) != len(cache.committee)) {
|
|
|
|
oldCommittee, newCommittee = cache.committee.toNotificationItem(), cache.newEpochCommittee.toNotificationItem()
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-01 15:15:36 +00:00
|
|
|
cache.nextValidators = cache.newEpochNextValidators
|
|
|
|
cache.committee = cache.newEpochCommittee
|
|
|
|
cache.committeeHash = cache.newEpochCommitteeHash
|
|
|
|
cache.votesChanged = false
|
2023-09-01 13:16:05 +00:00
|
|
|
|
|
|
|
// We need to put in storage anyway, as it affects dumps
|
|
|
|
ic.DAO.PutStorageItem(n.ID, prefixCommittee, cache.committee.Bytes(ic.DAO.GetItemCtx()))
|
2024-03-15 11:46:50 +00:00
|
|
|
|
|
|
|
if oldCommittee != nil {
|
|
|
|
ic.AddNotification(n.Hash, "CommitteeChanged", stackitem.NewArray([]stackitem.Item{
|
|
|
|
oldCommittee, newCommittee,
|
|
|
|
}))
|
|
|
|
}
|
2020-08-26 13:16:57 +00:00
|
|
|
}
|
2020-09-23 08:48:31 +00:00
|
|
|
return nil
|
|
|
|
}
|
2020-08-28 07:24:54 +00:00
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// PostPersist implements the Contract interface.
|
2020-09-23 08:48:31 +00:00
|
|
|
func (n *NEO) PostPersist(ic *interop.Context) error {
|
2020-09-28 07:51:25 +00:00
|
|
|
gas := n.GetGASPerBlock(ic.DAO, ic.Block.Index)
|
2022-04-27 15:14:42 +00:00
|
|
|
cache := ic.DAO.GetROCache(n.ID).(*NeoCache)
|
2023-09-01 13:00:53 +00:00
|
|
|
pubs := getCommitteeMembers(cache.committee)
|
2022-01-24 15:36:31 +00:00
|
|
|
committeeSize := n.cfg.GetCommitteeSize(ic.Block.Index)
|
2020-11-03 15:08:58 +00:00
|
|
|
index := int(ic.Block.Index) % committeeSize
|
2021-11-30 18:10:48 +00:00
|
|
|
committeeReward := new(big.Int).Mul(gas, bigCommitteeRewardRatio)
|
|
|
|
n.GAS.mint(ic, pubs[index].GetScriptHash(), committeeReward.Div(committeeReward, big100), false)
|
2020-11-03 15:08:58 +00:00
|
|
|
|
native: rework native NEO next block validators cache
Blockchain passes his own pure unwrapped DAO to
(*Blockchain).ComputeNextBlockValidators which means that native
RW NEO cache structure stored inside this DAO can be modified by
anyone who uses exported ComputeNextBlockValidators Blockchain API,
and technically it's valid, and we should allow this, because it's
the only purpose of `validators` caching. However, at the same time
some RPC server is allowed to request a subsequent wrapped DAO for
some test invocation. It means that descendant wrapped DAO
eventually will request RW NEO cache and try to `Copy()`
the underlying's DAO cache which is in direct use of
ComputeNextBlockValidators. Here's the race:
ComputeNextBlockValidators called by Consensus service tries to
update cached `validators` value, and descendant wrapped DAO
created by the RPC server tries to copy DAO's native cache and
read the cached `validators` value.
So the problem is that native cache not designated to handle
concurrent access between parent DAO layer and derived (wrapped)
DAO layer. I've carefully reviewed all the usages of native cache,
and turns out that the described situation is the only place where
parent DAO is used directly to modify its cache concurrently with
some descendant DAO that is trying to access the cache. All other
usages of native cache (not only NEO, but also all other native
contrcts) strictly rely on the hierarchical DAO structure and don't
try to perform these concurrent operations between DAO layers.
There's also persist operation, but it keeps cache RW lock taken,
so it doesn't have this problem as far. Thus, in this commit we rework
NEO's `validators` cache value so that it always contain the relevant
list for upper Blockchain's DAO and is updated every PostPersist (if
needed).
Note: we must be very careful extending our native cache in the
future, every usage of native cache must be checked against the
described problem.
Close #2989.
Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
2023-08-30 11:02:42 +00:00
|
|
|
var isCacheRW bool
|
2022-01-24 15:36:31 +00:00
|
|
|
if n.cfg.ShouldUpdateCommitteeAt(ic.Block.Index) {
|
2021-11-30 18:10:48 +00:00
|
|
|
var voterReward = new(big.Int).Set(bigVoterRewardRatio)
|
2020-11-03 15:08:58 +00:00
|
|
|
voterReward.Mul(voterReward, gas)
|
|
|
|
voterReward.Mul(voterReward, big.NewInt(voterRewardFactor*int64(committeeSize)))
|
2022-01-24 15:36:31 +00:00
|
|
|
var validatorsCount = n.cfg.GetNumOfCNs(ic.Block.Index)
|
2020-11-03 15:08:58 +00:00
|
|
|
voterReward.Div(voterReward, big.NewInt(int64(committeeSize+validatorsCount)))
|
2021-11-30 18:10:48 +00:00
|
|
|
voterReward.Div(voterReward, big100)
|
2020-11-03 15:08:58 +00:00
|
|
|
|
2022-04-27 15:14:42 +00:00
|
|
|
var (
|
native: rework native NEO next block validators cache
Blockchain passes his own pure unwrapped DAO to
(*Blockchain).ComputeNextBlockValidators which means that native
RW NEO cache structure stored inside this DAO can be modified by
anyone who uses exported ComputeNextBlockValidators Blockchain API,
and technically it's valid, and we should allow this, because it's
the only purpose of `validators` caching. However, at the same time
some RPC server is allowed to request a subsequent wrapped DAO for
some test invocation. It means that descendant wrapped DAO
eventually will request RW NEO cache and try to `Copy()`
the underlying's DAO cache which is in direct use of
ComputeNextBlockValidators. Here's the race:
ComputeNextBlockValidators called by Consensus service tries to
update cached `validators` value, and descendant wrapped DAO
created by the RPC server tries to copy DAO's native cache and
read the cached `validators` value.
So the problem is that native cache not designated to handle
concurrent access between parent DAO layer and derived (wrapped)
DAO layer. I've carefully reviewed all the usages of native cache,
and turns out that the described situation is the only place where
parent DAO is used directly to modify its cache concurrently with
some descendant DAO that is trying to access the cache. All other
usages of native cache (not only NEO, but also all other native
contrcts) strictly rely on the hierarchical DAO structure and don't
try to perform these concurrent operations between DAO layers.
There's also persist operation, but it keeps cache RW lock taken,
so it doesn't have this problem as far. Thus, in this commit we rework
NEO's `validators` cache value so that it always contain the relevant
list for upper Blockchain's DAO and is updated every PostPersist (if
needed).
Note: we must be very careful extending our native cache in the
future, every usage of native cache must be checked against the
described problem.
Close #2989.
Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
2023-08-30 11:02:42 +00:00
|
|
|
cs = cache.committee
|
|
|
|
key = make([]byte, 34)
|
2022-04-27 15:14:42 +00:00
|
|
|
)
|
2020-11-03 15:08:58 +00:00
|
|
|
for i := range cs {
|
|
|
|
if cs[i].Votes.Sign() > 0 {
|
2021-11-30 18:10:48 +00:00
|
|
|
var tmp = new(big.Int)
|
2020-11-03 15:08:58 +00:00
|
|
|
if i < validatorsCount {
|
2021-11-30 18:10:48 +00:00
|
|
|
tmp.Set(intTwo)
|
|
|
|
} else {
|
|
|
|
tmp.Set(intOne)
|
2020-11-03 15:08:58 +00:00
|
|
|
}
|
|
|
|
tmp.Mul(tmp, voterReward)
|
|
|
|
tmp.Div(tmp, cs[i].Votes)
|
|
|
|
|
|
|
|
key = makeVoterKey([]byte(cs[i].Key), key)
|
2023-04-20 02:46:19 +00:00
|
|
|
r := n.getLatestGASPerVote(ic.DAO, key)
|
|
|
|
tmp.Add(tmp, &r)
|
2021-07-01 07:33:04 +00:00
|
|
|
|
2022-04-27 15:14:42 +00:00
|
|
|
if !isCacheRW {
|
|
|
|
cache = ic.DAO.GetRWCache(n.ID).(*NeoCache)
|
|
|
|
isCacheRW = true
|
|
|
|
}
|
2022-04-12 14:29:11 +00:00
|
|
|
cache.gasPerVoteCache[cs[i].Key] = *tmp
|
2020-11-03 15:08:58 +00:00
|
|
|
|
2022-05-31 20:10:56 +00:00
|
|
|
ic.DAO.PutBigInt(n.ID, key, tmp)
|
2020-11-03 15:08:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-09-01 13:16:05 +00:00
|
|
|
// Update newEpoch cache for external users and further committee, committeeHash
|
|
|
|
// and nextBlockValidators cache initialisation if committee should be updated in
|
|
|
|
// the next block.
|
2023-09-01 12:10:42 +00:00
|
|
|
if n.cfg.ShouldUpdateCommitteeAt(ic.Block.Index + 1) {
|
|
|
|
var (
|
2023-09-01 12:33:01 +00:00
|
|
|
h = ic.Block.Index // consider persisting block as stored to get _next_ block newEpochNextValidators
|
2023-09-01 12:10:42 +00:00
|
|
|
numOfCNs = n.cfg.GetNumOfCNs(h + 1)
|
|
|
|
)
|
2023-09-01 15:15:36 +00:00
|
|
|
if cache.votesChanged ||
|
|
|
|
numOfCNs != len(cache.newEpochNextValidators) ||
|
|
|
|
n.cfg.GetCommitteeSize(h+1) != len(cache.newEpochCommittee) {
|
2023-09-01 12:10:42 +00:00
|
|
|
if !isCacheRW {
|
|
|
|
cache = ic.DAO.GetRWCache(n.ID).(*NeoCache)
|
|
|
|
}
|
2023-09-01 13:16:05 +00:00
|
|
|
err := n.updateCachedNewEpochValues(ic.DAO, cache, h, numOfCNs)
|
2023-09-01 12:10:42 +00:00
|
|
|
if err != nil {
|
2023-09-01 13:16:05 +00:00
|
|
|
return fmt.Errorf("failed to update next block newEpoch* cache: %w", err)
|
2023-09-01 12:10:42 +00:00
|
|
|
}
|
native: rework native NEO next block validators cache
Blockchain passes his own pure unwrapped DAO to
(*Blockchain).ComputeNextBlockValidators which means that native
RW NEO cache structure stored inside this DAO can be modified by
anyone who uses exported ComputeNextBlockValidators Blockchain API,
and technically it's valid, and we should allow this, because it's
the only purpose of `validators` caching. However, at the same time
some RPC server is allowed to request a subsequent wrapped DAO for
some test invocation. It means that descendant wrapped DAO
eventually will request RW NEO cache and try to `Copy()`
the underlying's DAO cache which is in direct use of
ComputeNextBlockValidators. Here's the race:
ComputeNextBlockValidators called by Consensus service tries to
update cached `validators` value, and descendant wrapped DAO
created by the RPC server tries to copy DAO's native cache and
read the cached `validators` value.
So the problem is that native cache not designated to handle
concurrent access between parent DAO layer and derived (wrapped)
DAO layer. I've carefully reviewed all the usages of native cache,
and turns out that the described situation is the only place where
parent DAO is used directly to modify its cache concurrently with
some descendant DAO that is trying to access the cache. All other
usages of native cache (not only NEO, but also all other native
contrcts) strictly rely on the hierarchical DAO structure and don't
try to perform these concurrent operations between DAO layers.
There's also persist operation, but it keeps cache RW lock taken,
so it doesn't have this problem as far. Thus, in this commit we rework
NEO's `validators` cache value so that it always contain the relevant
list for upper Blockchain's DAO and is updated every PostPersist (if
needed).
Note: we must be very careful extending our native cache in the
future, every usage of native cache must be checked against the
described problem.
Close #2989.
Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
2023-08-30 11:02:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-28 07:24:54 +00:00
|
|
|
return nil
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
2023-04-20 02:46:19 +00:00
|
|
|
func (n *NEO) getLatestGASPerVote(d *dao.Simple, key []byte) big.Int {
|
|
|
|
var g big.Int
|
|
|
|
cache := d.GetROCache(n.ID).(*NeoCache)
|
|
|
|
if g, ok := cache.gasPerVoteCache[string(key[1:])]; ok {
|
|
|
|
return g
|
|
|
|
}
|
|
|
|
item := d.GetStorageItem(n.ID, key)
|
|
|
|
if item == nil {
|
|
|
|
g = *big.NewInt(0)
|
|
|
|
} else {
|
|
|
|
g = *bigint.FromBytes(item)
|
|
|
|
}
|
|
|
|
return g
|
2020-11-03 15:08:58 +00:00
|
|
|
}
|
|
|
|
|
2022-05-13 11:49:41 +00:00
|
|
|
func (n *NEO) increaseBalance(ic *interop.Context, h util.Uint160, si *state.StorageItem, amount *big.Int, checkBal *big.Int) (func(), error) {
|
|
|
|
var postF func()
|
|
|
|
|
2021-07-18 09:39:31 +00:00
|
|
|
acc, err := state.NEOBalanceFromBytes(*si)
|
2020-04-23 18:28:37 +00:00
|
|
|
if err != nil {
|
2022-05-13 11:49:41 +00:00
|
|
|
return nil, err
|
2020-04-23 18:28:37 +00:00
|
|
|
}
|
2021-12-02 12:30:20 +00:00
|
|
|
if (amount.Sign() == -1 && acc.Balance.CmpAbs(amount) == -1) ||
|
2021-08-02 20:16:12 +00:00
|
|
|
(amount.Sign() == 0 && checkBal != nil && acc.Balance.Cmp(checkBal) == -1) {
|
2022-05-13 11:49:41 +00:00
|
|
|
return nil, errors.New("insufficient funds")
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
2022-05-13 11:49:41 +00:00
|
|
|
newGas, err := n.distributeGas(ic, acc)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if newGas != nil { // Can be if it was already distributed in the same block.
|
|
|
|
postF = func() { n.GAS.mint(ic, h, newGas, true) }
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
2020-04-26 09:51:17 +00:00
|
|
|
if amount.Sign() == 0 {
|
2022-05-31 17:10:20 +00:00
|
|
|
*si = acc.Bytes(ic.DAO.GetItemCtx())
|
2022-05-13 11:49:41 +00:00
|
|
|
return postF, nil
|
2020-04-26 09:51:17 +00:00
|
|
|
}
|
2020-09-29 19:38:38 +00:00
|
|
|
if err := n.ModifyAccountVotes(acc, ic.DAO, amount, false); err != nil {
|
2022-05-13 11:49:41 +00:00
|
|
|
return nil, err
|
2020-08-03 12:00:27 +00:00
|
|
|
}
|
|
|
|
if acc.VoteTo != nil {
|
|
|
|
if err := n.modifyVoterTurnout(ic.DAO, amount); err != nil {
|
2022-05-13 11:49:41 +00:00
|
|
|
return nil, err
|
2020-04-26 10:42:05 +00:00
|
|
|
}
|
2020-04-26 10:00:17 +00:00
|
|
|
}
|
2020-04-23 18:28:37 +00:00
|
|
|
acc.Balance.Add(&acc.Balance, amount)
|
2020-06-23 18:57:05 +00:00
|
|
|
if acc.Balance.Sign() != 0 {
|
2022-05-31 17:10:20 +00:00
|
|
|
*si = acc.Bytes(ic.DAO.GetItemCtx())
|
2020-06-23 18:57:05 +00:00
|
|
|
} else {
|
2021-03-05 14:06:54 +00:00
|
|
|
*si = nil
|
2020-06-23 18:57:05 +00:00
|
|
|
}
|
2022-05-13 11:49:41 +00:00
|
|
|
return postF, nil
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
2021-04-01 15:16:43 +00:00
|
|
|
func (n *NEO) balanceFromBytes(si *state.StorageItem) (*big.Int, error) {
|
2021-07-18 09:39:31 +00:00
|
|
|
acc, err := state.NEOBalanceFromBytes(*si)
|
2021-04-01 15:16:43 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &acc.Balance, err
|
|
|
|
}
|
|
|
|
|
2022-05-13 11:49:41 +00:00
|
|
|
func (n *NEO) distributeGas(ic *interop.Context, acc *state.NEOBalance) (*big.Int, error) {
|
2021-09-22 07:50:26 +00:00
|
|
|
if ic.Block == nil || ic.Block.Index == 0 || ic.Block.Index == acc.BalanceHeight {
|
2022-05-13 11:49:41 +00:00
|
|
|
return nil, nil
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
2023-04-20 02:46:19 +00:00
|
|
|
gen, err := n.calculateBonus(ic.DAO, acc, ic.Block.Index)
|
2020-08-26 09:07:30 +00:00
|
|
|
if err != nil {
|
2022-05-13 11:49:41 +00:00
|
|
|
return nil, err
|
2020-08-26 09:07:30 +00:00
|
|
|
}
|
2020-04-23 18:28:37 +00:00
|
|
|
acc.BalanceHeight = ic.Block.Index
|
2023-04-20 02:46:19 +00:00
|
|
|
if acc.VoteTo != nil {
|
|
|
|
latestGasPerVote := n.getLatestGASPerVote(ic.DAO, makeVoterKey(acc.VoteTo.Bytes()))
|
|
|
|
acc.LastGasPerVote = latestGasPerVote
|
|
|
|
}
|
2021-09-16 14:09:42 +00:00
|
|
|
|
2022-05-13 11:49:41 +00:00
|
|
|
return gen, nil
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
2020-06-03 12:55:06 +00:00
|
|
|
func (n *NEO) unclaimedGas(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
2020-03-25 10:00:11 +00:00
|
|
|
u := toUint160(args[0])
|
|
|
|
end := uint32(toBigInt(args[1]).Int64())
|
2023-04-20 02:46:19 +00:00
|
|
|
gen, err := n.CalculateBonus(ic, u, end)
|
2020-08-26 09:07:30 +00:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2020-07-09 09:57:24 +00:00
|
|
|
return stackitem.NewBigInteger(gen)
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
2020-08-26 10:06:19 +00:00
|
|
|
func (n *NEO) getGASPerBlock(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
2020-09-28 07:51:25 +00:00
|
|
|
gas := n.GetGASPerBlock(ic.DAO, ic.Block.Index)
|
2020-08-26 10:06:19 +00:00
|
|
|
return stackitem.NewBigInteger(gas)
|
|
|
|
}
|
|
|
|
|
2022-03-31 15:14:11 +00:00
|
|
|
func (n *NEO) getSortedGASRecordFromDAO(d *dao.Simple) gasRecord {
|
|
|
|
var gr = make(gasRecord, 0)
|
|
|
|
d.Seek(n.ID, storage.SeekRange{Prefix: []byte{prefixGASPerBlock}}, func(k, v []byte) bool {
|
|
|
|
gr = append(gr, gasIndexPair{
|
|
|
|
Index: binary.BigEndian.Uint32(k),
|
|
|
|
GASPerBlock: *bigint.FromBytes(v),
|
|
|
|
})
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
return gr
|
2020-09-28 07:51:25 +00:00
|
|
|
}
|
|
|
|
|
2020-08-26 10:06:19 +00:00
|
|
|
// GetGASPerBlock returns gas generated for block with provided index.
|
2022-02-16 15:04:47 +00:00
|
|
|
func (n *NEO) GetGASPerBlock(d *dao.Simple, index uint32) *big.Int {
|
2022-04-20 14:47:48 +00:00
|
|
|
cache := d.GetROCache(n.ID).(*NeoCache)
|
2022-04-19 16:40:27 +00:00
|
|
|
gr := cache.gasPerBlock
|
2020-08-26 10:06:19 +00:00
|
|
|
for i := len(gr) - 1; i >= 0; i-- {
|
|
|
|
if gr[i].Index <= index {
|
2020-09-28 07:51:25 +00:00
|
|
|
g := gr[i].GASPerBlock
|
|
|
|
return &g
|
2020-08-26 10:06:19 +00:00
|
|
|
}
|
|
|
|
}
|
2022-04-19 16:40:27 +00:00
|
|
|
panic("NEO cache not initialized")
|
2020-08-26 10:06:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetCommitteeAddress returns address of the committee.
|
2022-04-12 14:29:11 +00:00
|
|
|
func (n *NEO) GetCommitteeAddress(d *dao.Simple) util.Uint160 {
|
2022-04-20 14:47:48 +00:00
|
|
|
cache := d.GetROCache(n.ID).(*NeoCache)
|
2022-04-19 09:26:46 +00:00
|
|
|
return cache.committeeHash
|
2020-08-26 10:06:19 +00:00
|
|
|
}
|
|
|
|
|
2021-01-21 12:05:15 +00:00
|
|
|
func (n *NEO) checkCommittee(ic *interop.Context) bool {
|
2022-04-12 14:29:11 +00:00
|
|
|
ok, err := runtime.CheckHashedWitness(ic, n.GetCommitteeAddress(ic.DAO))
|
2021-01-21 12:05:15 +00:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
2020-08-26 10:06:19 +00:00
|
|
|
func (n *NEO) setGASPerBlock(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
|
|
gas := toBigInt(args[0])
|
2021-01-28 15:01:30 +00:00
|
|
|
err := n.SetGASPerBlock(ic, ic.Block.Index+1, gas)
|
2020-08-26 10:06:19 +00:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2021-01-28 15:01:30 +00:00
|
|
|
return stackitem.Null{}
|
2020-08-26 10:06:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// SetGASPerBlock sets gas generated for blocks after index.
|
2021-01-28 15:01:30 +00:00
|
|
|
func (n *NEO) SetGASPerBlock(ic *interop.Context, index uint32, gas *big.Int) error {
|
2020-08-26 10:06:19 +00:00
|
|
|
if gas.Sign() == -1 || gas.Cmp(big.NewInt(10*GASFactor)) == 1 {
|
2021-01-28 15:01:30 +00:00
|
|
|
return errors.New("invalid value for GASPerBlock")
|
2020-08-26 10:06:19 +00:00
|
|
|
}
|
2021-01-28 15:01:30 +00:00
|
|
|
if !n.checkCommittee(ic) {
|
|
|
|
return errors.New("invalid committee signature")
|
2020-08-26 10:06:19 +00:00
|
|
|
}
|
2022-02-16 14:48:15 +00:00
|
|
|
n.putGASRecord(ic.DAO, index, gas)
|
2022-04-20 14:47:48 +00:00
|
|
|
cache := ic.DAO.GetRWCache(n.ID).(*NeoCache)
|
2022-04-19 16:40:27 +00:00
|
|
|
cache.gasPerBlock = append(cache.gasPerBlock, gasIndexPair{
|
|
|
|
Index: index,
|
|
|
|
GASPerBlock: *gas,
|
|
|
|
})
|
2022-02-16 14:48:15 +00:00
|
|
|
return nil
|
2020-08-26 10:06:19 +00:00
|
|
|
}
|
|
|
|
|
2021-03-05 11:17:58 +00:00
|
|
|
func (n *NEO) getRegisterPrice(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
|
|
|
return stackitem.NewBigInteger(big.NewInt(n.getRegisterPriceInternal(ic.DAO)))
|
|
|
|
}
|
|
|
|
|
2022-02-16 15:04:47 +00:00
|
|
|
func (n *NEO) getRegisterPriceInternal(d *dao.Simple) int64 {
|
2022-04-20 14:47:48 +00:00
|
|
|
cache := d.GetROCache(n.ID).(*NeoCache)
|
2022-04-19 16:46:50 +00:00
|
|
|
return cache.registerPrice
|
2021-03-05 11:17:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (n *NEO) setRegisterPrice(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
|
|
price := toBigInt(args[0])
|
|
|
|
if price.Sign() <= 0 || !price.IsInt64() {
|
|
|
|
panic("invalid register price")
|
|
|
|
}
|
|
|
|
if !n.checkCommittee(ic) {
|
|
|
|
panic("invalid committee signature")
|
|
|
|
}
|
|
|
|
|
2022-02-16 14:48:15 +00:00
|
|
|
setIntWithKey(n.ID, ic.DAO, []byte{prefixRegisterPrice}, price.Int64())
|
2022-04-20 14:47:48 +00:00
|
|
|
cache := ic.DAO.GetRWCache(n.ID).(*NeoCache)
|
2022-04-19 16:46:50 +00:00
|
|
|
cache.registerPrice = price.Int64()
|
2021-03-05 11:17:58 +00:00
|
|
|
return stackitem.Null{}
|
|
|
|
}
|
|
|
|
|
2022-04-20 09:13:23 +00:00
|
|
|
func (n *NEO) dropCandidateIfZero(d *dao.Simple, cache *NeoCache, pub *keys.PublicKey, c *candidate) bool {
|
2020-11-03 15:08:58 +00:00
|
|
|
if c.Registered || c.Votes.Sign() != 0 {
|
2022-04-20 09:13:23 +00:00
|
|
|
return false
|
2020-11-03 15:08:58 +00:00
|
|
|
}
|
2022-02-16 14:48:15 +00:00
|
|
|
d.DeleteStorageItem(n.ID, makeValidatorKey(pub))
|
2020-11-03 15:08:58 +00:00
|
|
|
|
2021-07-01 07:33:04 +00:00
|
|
|
voterKey := makeVoterKey(pub.Bytes())
|
2023-04-20 02:46:19 +00:00
|
|
|
d.DeleteStorageItem(n.ID, voterKey)
|
2022-04-12 14:29:11 +00:00
|
|
|
delete(cache.gasPerVoteCache, string(voterKey))
|
2021-07-01 07:33:04 +00:00
|
|
|
|
2022-04-20 09:13:23 +00:00
|
|
|
return true
|
2020-11-03 15:08:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func makeVoterKey(pub []byte, prealloc ...[]byte) []byte {
|
|
|
|
var key []byte
|
|
|
|
if len(prealloc) != 0 {
|
|
|
|
key = prealloc[0]
|
|
|
|
} else {
|
2023-04-20 02:46:19 +00:00
|
|
|
key = make([]byte, 34)
|
2020-11-03 15:08:58 +00:00
|
|
|
}
|
|
|
|
key[0] = prefixVoterRewardPerCommittee
|
|
|
|
copy(key[1:], pub)
|
|
|
|
return key
|
|
|
|
}
|
|
|
|
|
|
|
|
// CalculateBonus calculates amount of gas generated for holding value NEO from start to end block
|
|
|
|
// and having voted for active committee member.
|
2023-04-20 02:46:19 +00:00
|
|
|
func (n *NEO) CalculateBonus(ic *interop.Context, acc util.Uint160, end uint32) (*big.Int, error) {
|
|
|
|
if ic.Block == nil || end != ic.Block.Index {
|
|
|
|
return nil, errors.New("can't calculate bonus of height unequal (BlockHeight + 1)")
|
|
|
|
}
|
2020-11-06 09:27:05 +00:00
|
|
|
key := makeAccountKey(acc)
|
2023-04-20 02:46:19 +00:00
|
|
|
si := ic.DAO.GetStorageItem(n.ID, key)
|
2020-11-06 09:27:05 +00:00
|
|
|
if si == nil {
|
|
|
|
return nil, storage.ErrKeyNotFound
|
|
|
|
}
|
2021-07-18 09:39:31 +00:00
|
|
|
st, err := state.NEOBalanceFromBytes(si)
|
2020-11-06 09:27:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-04-20 02:46:19 +00:00
|
|
|
return n.calculateBonus(ic.DAO, st, end)
|
2020-11-06 09:27:05 +00:00
|
|
|
}
|
|
|
|
|
2023-04-20 02:46:19 +00:00
|
|
|
func (n *NEO) calculateBonus(d *dao.Simple, acc *state.NEOBalance, end uint32) (*big.Int, error) {
|
|
|
|
r, err := n.CalculateNEOHolderReward(d, &acc.Balance, acc.BalanceHeight, end)
|
|
|
|
if err != nil || acc.VoteTo == nil {
|
2020-11-03 15:08:58 +00:00
|
|
|
return r, err
|
|
|
|
}
|
|
|
|
|
2023-04-20 02:46:19 +00:00
|
|
|
var key = makeVoterKey(acc.VoteTo.Bytes())
|
|
|
|
var reward = n.getLatestGASPerVote(d, key)
|
|
|
|
var tmp = big.NewInt(0).Sub(&reward, &acc.LastGasPerVote)
|
|
|
|
tmp.Mul(tmp, &acc.Balance)
|
2021-11-30 18:10:48 +00:00
|
|
|
tmp.Div(tmp, bigVoterRewardFactor)
|
2020-11-03 15:08:58 +00:00
|
|
|
tmp.Add(tmp, r)
|
|
|
|
return tmp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// CalculateNEOHolderReward return GAS reward for holding `value` of NEO from start to end block.
|
2022-02-16 15:04:47 +00:00
|
|
|
func (n *NEO) CalculateNEOHolderReward(d *dao.Simple, value *big.Int, start, end uint32) (*big.Int, error) {
|
2020-08-26 09:07:30 +00:00
|
|
|
if value.Sign() == 0 || start >= end {
|
|
|
|
return big.NewInt(0), nil
|
|
|
|
} else if value.Sign() < 0 {
|
|
|
|
return nil, errors.New("negative value")
|
|
|
|
}
|
2022-04-20 14:47:48 +00:00
|
|
|
cache := d.GetROCache(n.ID).(*NeoCache)
|
2022-04-19 16:40:27 +00:00
|
|
|
gr := cache.gasPerBlock
|
2020-08-26 09:07:30 +00:00
|
|
|
var sum, tmp big.Int
|
|
|
|
for i := len(gr) - 1; i >= 0; i-- {
|
|
|
|
if gr[i].Index >= end {
|
|
|
|
continue
|
|
|
|
} else if gr[i].Index <= start {
|
|
|
|
tmp.SetInt64(int64(end - start))
|
|
|
|
tmp.Mul(&tmp, &gr[i].GASPerBlock)
|
|
|
|
sum.Add(&sum, &tmp)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
tmp.SetInt64(int64(end - gr[i].Index))
|
|
|
|
tmp.Mul(&tmp, &gr[i].GASPerBlock)
|
|
|
|
sum.Add(&sum, &tmp)
|
|
|
|
end = gr[i].Index
|
|
|
|
}
|
|
|
|
res := new(big.Int).Mul(value, &sum)
|
|
|
|
res.Mul(res, tmp.SetInt64(neoHolderRewardRatio))
|
|
|
|
res.Div(res, tmp.SetInt64(100*NEOTotalSupply))
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
2020-08-03 08:43:51 +00:00
|
|
|
func (n *NEO) registerCandidate(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
2020-08-06 12:13:21 +00:00
|
|
|
pub := toPublicKey(args[0])
|
|
|
|
ok, err := runtime.CheckKeyedWitness(ic, pub)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
} else if !ok {
|
|
|
|
return stackitem.NewBool(false)
|
|
|
|
}
|
2021-03-05 11:17:58 +00:00
|
|
|
if !ic.VM.AddGas(n.getRegisterPriceInternal(ic.DAO)) {
|
|
|
|
panic("insufficient gas")
|
|
|
|
}
|
2020-08-06 12:13:21 +00:00
|
|
|
err = n.RegisterCandidateInternal(ic, pub)
|
2020-06-03 12:55:06 +00:00
|
|
|
return stackitem.NewBool(err == nil)
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
2020-08-03 13:24:22 +00:00
|
|
|
// RegisterCandidateInternal registers pub as a new candidate.
|
|
|
|
func (n *NEO) RegisterCandidateInternal(ic *interop.Context, pub *keys.PublicKey) error {
|
2022-05-27 12:33:17 +00:00
|
|
|
var emitEvent = true
|
|
|
|
|
2020-04-25 21:23:30 +00:00
|
|
|
key := makeValidatorKey(pub)
|
2021-02-09 09:26:25 +00:00
|
|
|
si := ic.DAO.GetStorageItem(n.ID, key)
|
2021-03-05 14:06:54 +00:00
|
|
|
var c *candidate
|
2020-08-03 12:00:27 +00:00
|
|
|
if si == nil {
|
2021-03-05 14:06:54 +00:00
|
|
|
c = &candidate{Registered: true}
|
2020-08-06 11:49:30 +00:00
|
|
|
} else {
|
2021-03-05 14:06:54 +00:00
|
|
|
c = new(candidate).FromBytes(si)
|
2022-05-27 12:33:17 +00:00
|
|
|
emitEvent = !c.Registered
|
2020-08-06 11:49:30 +00:00
|
|
|
c.Registered = true
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
2022-05-27 12:33:17 +00:00
|
|
|
err := putConvertibleToDAO(n.ID, ic.DAO, key, c)
|
|
|
|
if emitEvent {
|
2022-07-27 20:21:21 +00:00
|
|
|
cache := ic.DAO.GetRWCache(n.ID).(*NeoCache)
|
|
|
|
cache.votesChanged = true
|
2022-05-27 12:33:17 +00:00
|
|
|
ic.AddNotification(n.Hash, "CandidateStateChanged", stackitem.NewArray([]stackitem.Item{
|
|
|
|
stackitem.NewByteArray(pub.Bytes()),
|
|
|
|
stackitem.NewBool(c.Registered),
|
|
|
|
stackitem.NewBigInteger(&c.Votes),
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
return err
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
2020-08-06 11:57:10 +00:00
|
|
|
func (n *NEO) unregisterCandidate(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
2020-08-06 12:13:21 +00:00
|
|
|
pub := toPublicKey(args[0])
|
|
|
|
ok, err := runtime.CheckKeyedWitness(ic, pub)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
} else if !ok {
|
|
|
|
return stackitem.NewBool(false)
|
|
|
|
}
|
|
|
|
err = n.UnregisterCandidateInternal(ic, pub)
|
2020-08-06 11:57:10 +00:00
|
|
|
return stackitem.NewBool(err == nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnregisterCandidateInternal unregisters pub as a candidate.
|
|
|
|
func (n *NEO) UnregisterCandidateInternal(ic *interop.Context, pub *keys.PublicKey) error {
|
2022-05-27 12:33:17 +00:00
|
|
|
var err error
|
|
|
|
|
2020-08-06 11:57:10 +00:00
|
|
|
key := makeValidatorKey(pub)
|
2021-02-09 09:26:25 +00:00
|
|
|
si := ic.DAO.GetStorageItem(n.ID, key)
|
2020-08-06 11:57:10 +00:00
|
|
|
if si == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2022-04-20 14:47:48 +00:00
|
|
|
cache := ic.DAO.GetRWCache(n.ID).(*NeoCache)
|
2023-09-01 15:15:36 +00:00
|
|
|
// Not only current committee/validators cache is interested in votesChanged, but also
|
|
|
|
// newEpoch cache, thus, modify votesChanged to update the latter.
|
|
|
|
cache.votesChanged = true
|
2021-03-05 14:06:54 +00:00
|
|
|
c := new(candidate).FromBytes(si)
|
2022-05-27 12:33:17 +00:00
|
|
|
emitEvent := c.Registered
|
2020-08-06 11:57:10 +00:00
|
|
|
c.Registered = false
|
2022-04-20 09:13:23 +00:00
|
|
|
ok := n.dropCandidateIfZero(ic.DAO, cache, pub, c)
|
2022-05-27 12:33:17 +00:00
|
|
|
if !ok {
|
|
|
|
err = putConvertibleToDAO(n.ID, ic.DAO, key, c)
|
2020-11-03 15:08:58 +00:00
|
|
|
}
|
2022-05-27 12:33:17 +00:00
|
|
|
if emitEvent {
|
|
|
|
ic.AddNotification(n.Hash, "CandidateStateChanged", stackitem.NewArray([]stackitem.Item{
|
|
|
|
stackitem.NewByteArray(pub.Bytes()),
|
|
|
|
stackitem.NewBool(c.Registered),
|
|
|
|
stackitem.NewBigInteger(&c.Votes),
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
return err
|
2020-08-06 11:57:10 +00:00
|
|
|
}
|
|
|
|
|
2020-06-03 12:55:06 +00:00
|
|
|
func (n *NEO) vote(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
2020-03-25 10:00:11 +00:00
|
|
|
acc := toUint160(args[0])
|
2020-08-03 12:00:27 +00:00
|
|
|
var pub *keys.PublicKey
|
|
|
|
if _, ok := args[1].(stackitem.Null); !ok {
|
|
|
|
pub = toPublicKey(args[1])
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
2020-08-03 12:00:27 +00:00
|
|
|
err := n.VoteInternal(ic, acc, pub)
|
2020-06-03 12:55:06 +00:00
|
|
|
return stackitem.NewBool(err == nil)
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// VoteInternal votes from account h for validarors specified in pubs.
|
2020-08-03 12:00:27 +00:00
|
|
|
func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pub *keys.PublicKey) error {
|
2020-07-15 19:43:30 +00:00
|
|
|
ok, err := runtime.CheckHashedWitness(ic, h)
|
2020-03-25 10:00:11 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
} else if !ok {
|
|
|
|
return errors.New("invalid signature")
|
|
|
|
}
|
2020-04-23 18:28:37 +00:00
|
|
|
key := makeAccountKey(h)
|
2021-02-09 09:26:25 +00:00
|
|
|
si := ic.DAO.GetStorageItem(n.ID, key)
|
2020-04-23 18:28:37 +00:00
|
|
|
if si == nil {
|
|
|
|
return errors.New("invalid account")
|
|
|
|
}
|
2021-07-18 09:39:31 +00:00
|
|
|
acc, err := state.NEOBalanceFromBytes(si)
|
2020-03-25 10:00:11 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-03-29 13:07:28 +00:00
|
|
|
// we should put it in storage anyway as it affects dumps
|
2022-02-16 14:48:15 +00:00
|
|
|
ic.DAO.PutStorageItem(n.ID, key, si)
|
2021-03-29 13:00:10 +00:00
|
|
|
if pub != nil {
|
|
|
|
valKey := makeValidatorKey(pub)
|
|
|
|
valSi := ic.DAO.GetStorageItem(n.ID, valKey)
|
|
|
|
if valSi == nil {
|
|
|
|
return errors.New("unknown validator")
|
|
|
|
}
|
|
|
|
cd := new(candidate).FromBytes(valSi)
|
2021-03-29 14:06:37 +00:00
|
|
|
// we should put it in storage anyway as it affects dumps
|
2022-02-16 14:48:15 +00:00
|
|
|
ic.DAO.PutStorageItem(n.ID, valKey, valSi)
|
2021-03-29 13:00:10 +00:00
|
|
|
if !cd.Registered {
|
|
|
|
return errors.New("validator must be registered")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-03 12:00:27 +00:00
|
|
|
if (acc.VoteTo == nil) != (pub == nil) {
|
|
|
|
val := &acc.Balance
|
|
|
|
if pub == nil {
|
|
|
|
val = new(big.Int).Neg(val)
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
2020-08-03 12:00:27 +00:00
|
|
|
if err := n.modifyVoterTurnout(ic.DAO, val); err != nil {
|
2020-03-25 10:00:11 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2022-05-13 11:49:41 +00:00
|
|
|
newGas, err := n.distributeGas(ic, acc)
|
|
|
|
if err != nil {
|
2020-11-03 15:08:58 +00:00
|
|
|
return err
|
|
|
|
}
|
2020-09-29 19:38:38 +00:00
|
|
|
if err := n.ModifyAccountVotes(acc, ic.DAO, new(big.Int).Neg(&acc.Balance), false); err != nil {
|
2020-08-03 12:00:27 +00:00
|
|
|
return err
|
|
|
|
}
|
2023-04-20 02:46:19 +00:00
|
|
|
if pub != nil && pub != acc.VoteTo {
|
|
|
|
acc.LastGasPerVote = n.getLatestGASPerVote(ic.DAO, makeVoterKey(pub.Bytes()))
|
|
|
|
}
|
2022-05-27 12:33:17 +00:00
|
|
|
oldVote := acc.VoteTo
|
2020-08-03 12:00:27 +00:00
|
|
|
acc.VoteTo = pub
|
2020-09-29 19:38:38 +00:00
|
|
|
if err := n.ModifyAccountVotes(acc, ic.DAO, &acc.Balance, true); err != nil {
|
2020-04-23 18:28:37 +00:00
|
|
|
return err
|
|
|
|
}
|
2024-03-14 14:23:36 +00:00
|
|
|
if pub == nil {
|
|
|
|
acc.LastGasPerVote = *big.NewInt(0)
|
|
|
|
}
|
2022-05-31 17:10:20 +00:00
|
|
|
ic.DAO.PutStorageItem(n.ID, key, acc.Bytes(ic.DAO.GetItemCtx()))
|
2022-05-27 12:33:17 +00:00
|
|
|
|
|
|
|
ic.AddNotification(n.Hash, "Vote", stackitem.NewArray([]stackitem.Item{
|
|
|
|
stackitem.NewByteArray(h.BytesBE()),
|
|
|
|
keyToStackItem(oldVote),
|
|
|
|
keyToStackItem(pub),
|
|
|
|
stackitem.NewBigInteger(&acc.Balance),
|
|
|
|
}))
|
|
|
|
|
2022-05-13 11:49:41 +00:00
|
|
|
if newGas != nil { // Can be if it was already distributed in the same block.
|
|
|
|
n.GAS.mint(ic, h, newGas, true)
|
|
|
|
}
|
2022-02-16 14:48:15 +00:00
|
|
|
return nil
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
2022-05-27 12:33:17 +00:00
|
|
|
func keyToStackItem(k *keys.PublicKey) stackitem.Item {
|
|
|
|
if k == nil {
|
|
|
|
return stackitem.Null{}
|
|
|
|
}
|
|
|
|
return stackitem.NewByteArray(k.Bytes())
|
|
|
|
}
|
|
|
|
|
2020-03-25 10:00:11 +00:00
|
|
|
// ModifyAccountVotes modifies votes of the specified account by value (can be negative).
|
2020-08-14 09:16:24 +00:00
|
|
|
// typ specifies if this modify is occurring during transfer or vote (with old or new validator).
|
2022-02-16 15:04:47 +00:00
|
|
|
func (n *NEO) ModifyAccountVotes(acc *state.NEOBalance, d *dao.Simple, value *big.Int, isNewVote bool) error {
|
2022-04-20 14:47:48 +00:00
|
|
|
cache := d.GetRWCache(n.ID).(*NeoCache)
|
2022-04-19 09:26:46 +00:00
|
|
|
cache.votesChanged = true
|
2020-08-03 12:00:27 +00:00
|
|
|
if acc.VoteTo != nil {
|
|
|
|
key := makeValidatorKey(acc.VoteTo)
|
2021-02-09 09:26:25 +00:00
|
|
|
si := d.GetStorageItem(n.ID, key)
|
2020-04-25 21:23:30 +00:00
|
|
|
if si == nil {
|
|
|
|
return errors.New("invalid validator")
|
|
|
|
}
|
2021-03-05 14:06:54 +00:00
|
|
|
cd := new(candidate).FromBytes(si)
|
2020-08-03 12:00:27 +00:00
|
|
|
cd.Votes.Add(&cd.Votes, value)
|
2020-09-29 19:38:38 +00:00
|
|
|
if !isNewVote {
|
2022-04-20 09:13:23 +00:00
|
|
|
ok := n.dropCandidateIfZero(d, cache, acc.VoteTo, cd)
|
2020-11-03 15:08:58 +00:00
|
|
|
if ok {
|
2022-04-20 09:13:23 +00:00
|
|
|
return nil
|
2020-08-03 12:00:27 +00:00
|
|
|
}
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
2021-07-17 15:37:33 +00:00
|
|
|
return putConvertibleToDAO(n.ID, d, key, cd)
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-05-04 14:00:18 +00:00
|
|
|
func (n *NEO) getCandidates(d *dao.Simple, sortByKey bool, max int) ([]keyWithVotes, error) {
|
2022-03-31 15:14:11 +00:00
|
|
|
arr := make([]keyWithVotes, 0)
|
2022-04-27 19:58:52 +00:00
|
|
|
buf := io.NewBufBinWriter()
|
2022-03-31 15:14:11 +00:00
|
|
|
d.Seek(n.ID, storage.SeekRange{Prefix: []byte{prefixCandidate}}, func(k, v []byte) bool {
|
|
|
|
c := new(candidate).FromBytes(v)
|
2022-04-27 19:58:52 +00:00
|
|
|
emit.CheckSig(buf.BinWriter, k)
|
2022-04-19 15:57:46 +00:00
|
|
|
if c.Registered && !n.Policy.IsBlocked(d, hash.Hash160(buf.Bytes())) {
|
2022-03-31 15:14:11 +00:00
|
|
|
arr = append(arr, keyWithVotes{Key: string(k), Votes: &c.Votes})
|
2020-08-03 12:00:27 +00:00
|
|
|
}
|
2022-04-27 19:58:52 +00:00
|
|
|
buf.Reset()
|
2022-05-04 14:00:18 +00:00
|
|
|
return !sortByKey || max > 0 && len(arr) < max
|
2022-03-31 15:14:11 +00:00
|
|
|
})
|
|
|
|
|
2021-09-24 14:22:45 +00:00
|
|
|
if !sortByKey {
|
|
|
|
// sortByKey assumes to sort by serialized key bytes (that's the way keys
|
|
|
|
// are stored and retrieved from the storage by default). Otherwise, need
|
|
|
|
// to sort using big.Int comparator.
|
2020-11-03 15:08:58 +00:00
|
|
|
sort.Slice(arr, func(i, j int) bool {
|
|
|
|
// The most-voted validators should end up in the front of the list.
|
|
|
|
cmp := arr[i].Votes.Cmp(arr[j].Votes)
|
|
|
|
if cmp != 0 {
|
|
|
|
return cmp > 0
|
|
|
|
}
|
2021-06-16 14:31:27 +00:00
|
|
|
// Ties are broken with deserialized public keys.
|
|
|
|
// Sort by ECPoint's (X, Y) components: compare X first, and then compare Y.
|
|
|
|
cmpX := strings.Compare(arr[i].Key[1:], arr[j].Key[1:])
|
|
|
|
if cmpX != 0 {
|
|
|
|
return cmpX == -1
|
|
|
|
}
|
|
|
|
// The case when X components are the same is extremely rare, thus we perform
|
|
|
|
// key deserialization only if needed. No error can occur.
|
|
|
|
ki, _ := keys.NewPublicKeyFromBytes([]byte(arr[i].Key), elliptic.P256())
|
|
|
|
kj, _ := keys.NewPublicKeyFromBytes([]byte(arr[j].Key), elliptic.P256())
|
|
|
|
return ki.Y.Cmp(kj.Y) == -1
|
2020-11-03 15:08:58 +00:00
|
|
|
})
|
|
|
|
}
|
2020-04-25 21:23:30 +00:00
|
|
|
return arr, nil
|
|
|
|
}
|
|
|
|
|
2020-08-03 08:43:51 +00:00
|
|
|
// GetCandidates returns current registered validators list with keys
|
2020-04-26 17:04:16 +00:00
|
|
|
// and votes.
|
2022-02-16 15:04:47 +00:00
|
|
|
func (n *NEO) GetCandidates(d *dao.Simple) ([]state.Validator, error) {
|
2022-05-04 14:00:18 +00:00
|
|
|
kvs, err := n.getCandidates(d, true, maxGetCandidatesRespLen)
|
2020-04-26 17:04:16 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
arr := make([]state.Validator, len(kvs))
|
|
|
|
for i := range kvs {
|
2020-07-13 09:59:41 +00:00
|
|
|
arr[i].Key, err = keys.NewPublicKeyFromBytes([]byte(kvs[i].Key), elliptic.P256())
|
2020-04-26 17:04:16 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
arr[i].Votes = kvs[i].Votes
|
|
|
|
}
|
|
|
|
return arr, nil
|
|
|
|
}
|
|
|
|
|
2020-08-03 08:43:51 +00:00
|
|
|
func (n *NEO) getCandidatesCall(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
2022-05-04 14:00:18 +00:00
|
|
|
validators, err := n.getCandidates(ic.DAO, true, maxGetCandidatesRespLen)
|
2020-04-25 21:23:30 +00:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2020-06-03 12:55:06 +00:00
|
|
|
arr := make([]stackitem.Item, len(validators))
|
2020-04-25 21:23:30 +00:00
|
|
|
for i := range validators {
|
2020-06-03 12:55:06 +00:00
|
|
|
arr[i] = stackitem.NewStruct([]stackitem.Item{
|
|
|
|
stackitem.NewByteArray([]byte(validators[i].Key)),
|
|
|
|
stackitem.NewBigInteger(validators[i].Votes),
|
2020-03-25 10:00:11 +00:00
|
|
|
})
|
|
|
|
}
|
2020-06-03 12:55:06 +00:00
|
|
|
return stackitem.NewArray(arr)
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
2022-05-04 14:00:18 +00:00
|
|
|
func (n *NEO) getAllCandidatesCall(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
prefix := []byte{prefixCandidate}
|
|
|
|
buf := io.NewBufBinWriter()
|
|
|
|
keep := func(kv storage.KeyValue) bool {
|
|
|
|
c := new(candidate).FromBytes(kv.Value)
|
|
|
|
emit.CheckSig(buf.BinWriter, kv.Key)
|
|
|
|
if c.Registered && !n.Policy.IsBlocked(ic.DAO, hash.Hash160(buf.Bytes())) {
|
|
|
|
buf.Reset()
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
buf.Reset()
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
seekres := ic.DAO.SeekAsync(ctx, n.ID, storage.SeekRange{Prefix: prefix})
|
|
|
|
filteredRes := make(chan storage.KeyValue)
|
|
|
|
go func() {
|
|
|
|
for kv := range seekres {
|
|
|
|
if keep(kv) {
|
|
|
|
filteredRes <- kv
|
|
|
|
}
|
|
|
|
}
|
|
|
|
close(filteredRes)
|
|
|
|
}()
|
|
|
|
|
|
|
|
opts := istorage.FindRemovePrefix | istorage.FindDeserialize | istorage.FindPick1
|
|
|
|
item := istorage.NewIterator(filteredRes, prefix, int64(opts))
|
core: ensure memcached Seek operation is properly cancelled
PR #2495 is not enought, the problem is reproduced during dump restore
with the stacktrace attached down below.
The reason is that SeekAsync starts goroutine that performs Seek
operation over private MemCachedStore. The goroutine iterates over
MemCachedStore's map without holding the lock (it's OK, because it's
private), whereas the main thread continues invocation and
performs writes to the same map. To fix that ic.Exec() needs to
wait until all goroutines are properly exited.
```
2022-05-17T08:34:50.430+0300 INFO persisted to disk {"blocks": 632, "keys": 16546, "headerHeight": 54091, "blockHeight": 54091, "took": "57.904387ms"}
fatal error: concurrent map iteration and map write
goroutine 218853 [running]:
runtime.throw({0xf56a31, 0x614968})
runtime/panic.go:1198 +0x71 fp=0xc0000ceae0 sp=0xc0000ceab0 pc=0x4357b1
runtime.mapiternext(0x0)
runtime/map.go:858 +0x4eb fp=0xc0000ceb50 sp=0xc0000ceae0 pc=0x40f82b
runtime.mapiterinit(0xc0000cec18, 0xc00359ca40, 0x1a)
runtime/map.go:848 +0x236 fp=0xc0000ceb70 sp=0xc0000ceb50 pc=0x40f2f6
github.com/nspcc-dev/neo-go/pkg/core/storage.(*MemCachedStore).prepareSeekMemSnapshot(0xc005315680, {{0xc00359ca40, 0x1a, 0x1a}, {0x0, 0x0, 0x0}, 0x0})
github.com/nspcc-dev/neo-go/pkg/core/storage/memcached_store.go:209 +0x24a fp=0xc0000ced88 sp=0xc0000ceb70 pc=0x823fca
github.com/nspcc-dev/neo-go/pkg/core/storage.(*MemCachedStore).Seek(0xd94ac0, {{0xc00359ca40, 0x1a, 0x1a}, {0x0, 0x0, 0x0}, 0x0}, 0x20)
github.com/nspcc-dev/neo-go/pkg/core/storage/memcached_store.go:156 +0x9b fp=0xc0000cee20 sp=0xc0000ced88 pc=0x82369b
github.com/nspcc-dev/neo-go/pkg/core/storage.performSeek({0x11c3330, 0xc002240dc0}, {0x11cb258, 0xc005315680}, {0x0, 0x0, 0x1}, {{0xc00359ca40, 0x1a, 0x1a}, ...}, ...)
github.com/nspcc-dev/neo-go/pkg/core/storage/memcached_store.go:304 +0x4b7 fp=0xc0000cef00 sp=0xc0000cee20 pc=0x824a37
github.com/nspcc-dev/neo-go/pkg/core/storage.(*MemCachedStore).SeekAsync.func1()
github.com/nspcc-dev/neo-go/pkg/core/storage/memcached_store.go:176 +0x16c fp=0xc0000cefe0 sp=0xc0000cef00 pc=0x823b8c
runtime.goexit()
runtime/asm_amd64.s:1581 +0x1 fp=0xc0000cefe8 sp=0xc0000cefe0 pc=0x468a41
created by github.com/nspcc-dev/neo-go/pkg/core/storage.(*MemCachedStore).SeekAsync
github.com/nspcc-dev/neo-go/pkg/core/storage/memcached_store.go:175 +0x1f6
goroutine 1 [runnable]:
github.com/nspcc-dev/neo-go/pkg/core/mpt.MapToMPTBatch.func1(0x4, 0x0)
github.com/nspcc-dev/neo-go/pkg/core/mpt/batch.go:28 +0x95
sort.doPivot_func({0xc000457878, 0xc003305bc0}, 0x0, 0x1a)
sort/zfuncversion.go:121 +0x46a
sort.quickSort_func({0xc000457878, 0xc003305bc0}, 0xc001014d20, 0x0, 0xc003304ff0)
sort/zfuncversion.go:143 +0x85
sort.Slice({0xd94800, 0xc001014d20}, 0x1a)
sort/slice.go:20 +0x9f
github.com/nspcc-dev/neo-go/pkg/core/mpt.MapToMPTBatch(0xc003304ff0)
github.com/nspcc-dev/neo-go/pkg/core/mpt/batch.go:28 +0x2e7
github.com/nspcc-dev/neo-go/pkg/core.(*Blockchain).storeBlock(0xc000400280, 0xc000f178c0, 0xc005a04370)
github.com/nspcc-dev/neo-go/pkg/core/blockchain.go:1136 +0xfef
github.com/nspcc-dev/neo-go/pkg/core.(*Blockchain).AddBlock(0xc000400280, 0xc000f178c0)
github.com/nspcc-dev/neo-go/pkg/core/blockchain.go:893 +0x755
github.com/nspcc-dev/neo-go/pkg/core/chaindump.Restore({0x11c3528, 0xc000400280}, 0xc350, 0x0, 0x4c221, 0xc000458710)
github.com/nspcc-dev/neo-go/pkg/core/chaindump/dump.go:73 +0x2ca
github.com/nspcc-dev/neo-go/cli/server.restoreDB(0xc000410000)
github.com/nspcc-dev/neo-go/cli/server/server.go:381 +0xcfa
github.com/urfave/cli.HandleAction({0xdbbfa0, 0x10a1078}, 0x7)
github.com/urfave/cli@v1.22.5/app.go:524 +0xa8
github.com/urfave/cli.Command.Run({{0xf32b10, 0x7}, {0x0, 0x0}, {0x0, 0x0, 0x0}, {0xf4a41d, 0x1c}, {0x0, ...}, ...}, ...)
github.com/urfave/cli@v1.22.5/command.go:173 +0x652
github.com/urfave/cli.(*App).RunAsSubcommand(0xc0001f7880, 0xc0001adce0)
github.com/urfave/cli@v1.22.5/app.go:405 +0x9ec
github.com/urfave/cli.Command.startApp({{0xf2e982, 0x2}, {0x0, 0x0}, {0x0, 0x0, 0x0}, {0xf41af9, 0x16}, {0x0, ...}, ...}, ...)
github.com/urfave/cli@v1.22.5/command.go:372 +0x6e9
github.com/urfave/cli.Command.Run({{0xf2e982, 0x2}, {0x0, 0x0}, {0x0, 0x0, 0x0}, {0xf41af9, 0x16}, {0x0, ...}, ...}, ...)
github.com/urfave/cli@v1.22.5/command.go:102 +0x808
github.com/urfave/cli.(*App).Run(0xc0001f76c0, {0xc00012e000, 0x8, 0x8})
github.com/urfave/cli@v1.22.5/app.go:277 +0x705
main.main()
./main.go:19 +0x33
goroutine 66 [select]:
github.com/syndtr/goleveldb/leveldb/util.(*BufferPool).drain(0xc00027e1c0)
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/util/buffer_pool.go:209 +0xcd
created by github.com/syndtr/goleveldb/leveldb/util.NewBufferPool
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/util/buffer_pool.go:240 +0x19b
goroutine 67 [select]:
github.com/syndtr/goleveldb/leveldb.(*session).refLoop(0xc0002441e0)
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/session_util.go:189 +0x5bb
created by github.com/syndtr/goleveldb/leveldb.newSession
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/session.go:93 +0x2de
goroutine 85 [select]:
github.com/syndtr/goleveldb/leveldb.(*DB).compactionError(0xc0001f7a40)
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/db_compaction.go:91 +0x15e
created by github.com/syndtr/goleveldb/leveldb.openDB
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/db.go:148 +0x4ef
goroutine 86 [select]:
github.com/syndtr/goleveldb/leveldb.(*DB).mpoolDrain(0xc0001f7a40)
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/db_state.go:101 +0xae
created by github.com/syndtr/goleveldb/leveldb.openDB
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/db.go:149 +0x531
goroutine 87 [runnable]:
github.com/syndtr/goleveldb/leveldb.(*compaction).shouldStopBefore(0xc001787440, {0xc001890720, 0x29, 0x30})
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/session_compaction.go:272 +0x10a
github.com/syndtr/goleveldb/leveldb.(*tableCompactionBuilder).run(0xc0001ca140, 0xc002039638)
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/db_compaction.go:461 +0x565
github.com/syndtr/goleveldb/leveldb.(*DB).compactionTransact(0xc0001f7a40, {0xf369f0, 0xb}, {0x11b9b60, 0xc0001ca140})
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/db_compaction.go:186 +0x22f
github.com/syndtr/goleveldb/leveldb.(*DB).tableCompaction(0xc0001f7a40, 0xc001787440, 0x0)
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/db_compaction.go:580 +0xa30
github.com/syndtr/goleveldb/leveldb.(*DB).tableAutoCompaction(0xc0001f7a40)
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/db_compaction.go:644 +0x39
github.com/syndtr/goleveldb/leveldb.(*DB).tCompaction(0xc0001f7a40)
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/db_compaction.go:863 +0x43b
created by github.com/syndtr/goleveldb/leveldb.openDB
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/db.go:155 +0x5a7
goroutine 88 [select]:
github.com/syndtr/goleveldb/leveldb.(*DB).mCompaction(0xc0001f7a40)
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/db_compaction.go:773 +0x119
created by github.com/syndtr/goleveldb/leveldb.openDB
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/db.go:156 +0x5e9
goroutine 78 [select]:
github.com/nspcc-dev/neo-go/pkg/core.(*Blockchain).Run(0xc000400280)
github.com/nspcc-dev/neo-go/pkg/core/blockchain.go:615 +0x185
created by github.com/nspcc-dev/neo-go/cli/server.initBCWithMetrics
github.com/nspcc-dev/neo-go/cli/server/server.go:217 +0x23d
goroutine 79 [IO wait]:
internal/poll.runtime_pollWait(0x7ffb00b2aad8, 0x72)
runtime/netpoll.go:229 +0x89
internal/poll.(*pollDesc).wait(0xc00012e180, 0x4, 0x0)
internal/poll/fd_poll_runtime.go:84 +0x32
internal/poll.(*pollDesc).waitRead(...)
internal/poll/fd_poll_runtime.go:89
internal/poll.(*FD).Accept(0xc00012e180)
internal/poll/fd_unix.go:402 +0x22c
net.(*netFD).accept(0xc00012e180)
net/fd_unix.go:173 +0x35
net.(*TCPListener).accept(0xc000280090)
net/tcpsock_posix.go:140 +0x28
net.(*TCPListener).Accept(0xc000280090)
net/tcpsock.go:262 +0x3d
net/http.(*Server).Serve(0xc00027e380, {0x11bd4a8, 0xc000280090})
net/http/server.go:3001 +0x394
net/http.(*Server).ListenAndServe(0xc00027e380)
net/http/server.go:2930 +0x7d
github.com/nspcc-dev/neo-go/pkg/network/metrics.(*Service).Start(0xc000097d10)
github.com/nspcc-dev/neo-go/pkg/network/metrics/metrics.go:29 +0x115
created by github.com/nspcc-dev/neo-go/cli/server.initBCWithMetrics
github.com/nspcc-dev/neo-go/cli/server/server.go:218 +0x285
goroutine 9 [syscall]:
os/signal.signal_recv()
runtime/sigqueue.go:169 +0x98
os/signal.loop()
os/signal/signal_unix.go:24 +0x19
created by os/signal.Notify.func1.1
os/signal/signal.go:151 +0x2c
goroutine 8 [select]:
github.com/nspcc-dev/neo-go/pkg/core.(*Blockchain).notificationDispatcher(0xc000400280)
github.com/nspcc-dev/neo-go/pkg/core/blockchain.go:739 +0x2e7
created by github.com/nspcc-dev/neo-go/pkg/core.(*Blockchain).Run
github.com/nspcc-dev/neo-go/pkg/core/blockchain.go:612 +0xe5
goroutine 10 [chan receive]:
github.com/nspcc-dev/neo-go/cli/server.newGraceContext.func1()
github.com/nspcc-dev/neo-go/cli/server/server.go:117 +0x28
created by github.com/nspcc-dev/neo-go/cli/server.newGraceContext
github.com/nspcc-dev/neo-go/cli/server/server.go:116 +0xde
```
2022-05-17 05:59:48 +00:00
|
|
|
ic.RegisterCancelFunc(func() {
|
|
|
|
cancel()
|
2023-03-29 03:19:23 +00:00
|
|
|
for range seekres { //nolint:revive //empty-block
|
core: ensure memcached Seek operation is properly cancelled
PR #2495 is not enought, the problem is reproduced during dump restore
with the stacktrace attached down below.
The reason is that SeekAsync starts goroutine that performs Seek
operation over private MemCachedStore. The goroutine iterates over
MemCachedStore's map without holding the lock (it's OK, because it's
private), whereas the main thread continues invocation and
performs writes to the same map. To fix that ic.Exec() needs to
wait until all goroutines are properly exited.
```
2022-05-17T08:34:50.430+0300 INFO persisted to disk {"blocks": 632, "keys": 16546, "headerHeight": 54091, "blockHeight": 54091, "took": "57.904387ms"}
fatal error: concurrent map iteration and map write
goroutine 218853 [running]:
runtime.throw({0xf56a31, 0x614968})
runtime/panic.go:1198 +0x71 fp=0xc0000ceae0 sp=0xc0000ceab0 pc=0x4357b1
runtime.mapiternext(0x0)
runtime/map.go:858 +0x4eb fp=0xc0000ceb50 sp=0xc0000ceae0 pc=0x40f82b
runtime.mapiterinit(0xc0000cec18, 0xc00359ca40, 0x1a)
runtime/map.go:848 +0x236 fp=0xc0000ceb70 sp=0xc0000ceb50 pc=0x40f2f6
github.com/nspcc-dev/neo-go/pkg/core/storage.(*MemCachedStore).prepareSeekMemSnapshot(0xc005315680, {{0xc00359ca40, 0x1a, 0x1a}, {0x0, 0x0, 0x0}, 0x0})
github.com/nspcc-dev/neo-go/pkg/core/storage/memcached_store.go:209 +0x24a fp=0xc0000ced88 sp=0xc0000ceb70 pc=0x823fca
github.com/nspcc-dev/neo-go/pkg/core/storage.(*MemCachedStore).Seek(0xd94ac0, {{0xc00359ca40, 0x1a, 0x1a}, {0x0, 0x0, 0x0}, 0x0}, 0x20)
github.com/nspcc-dev/neo-go/pkg/core/storage/memcached_store.go:156 +0x9b fp=0xc0000cee20 sp=0xc0000ced88 pc=0x82369b
github.com/nspcc-dev/neo-go/pkg/core/storage.performSeek({0x11c3330, 0xc002240dc0}, {0x11cb258, 0xc005315680}, {0x0, 0x0, 0x1}, {{0xc00359ca40, 0x1a, 0x1a}, ...}, ...)
github.com/nspcc-dev/neo-go/pkg/core/storage/memcached_store.go:304 +0x4b7 fp=0xc0000cef00 sp=0xc0000cee20 pc=0x824a37
github.com/nspcc-dev/neo-go/pkg/core/storage.(*MemCachedStore).SeekAsync.func1()
github.com/nspcc-dev/neo-go/pkg/core/storage/memcached_store.go:176 +0x16c fp=0xc0000cefe0 sp=0xc0000cef00 pc=0x823b8c
runtime.goexit()
runtime/asm_amd64.s:1581 +0x1 fp=0xc0000cefe8 sp=0xc0000cefe0 pc=0x468a41
created by github.com/nspcc-dev/neo-go/pkg/core/storage.(*MemCachedStore).SeekAsync
github.com/nspcc-dev/neo-go/pkg/core/storage/memcached_store.go:175 +0x1f6
goroutine 1 [runnable]:
github.com/nspcc-dev/neo-go/pkg/core/mpt.MapToMPTBatch.func1(0x4, 0x0)
github.com/nspcc-dev/neo-go/pkg/core/mpt/batch.go:28 +0x95
sort.doPivot_func({0xc000457878, 0xc003305bc0}, 0x0, 0x1a)
sort/zfuncversion.go:121 +0x46a
sort.quickSort_func({0xc000457878, 0xc003305bc0}, 0xc001014d20, 0x0, 0xc003304ff0)
sort/zfuncversion.go:143 +0x85
sort.Slice({0xd94800, 0xc001014d20}, 0x1a)
sort/slice.go:20 +0x9f
github.com/nspcc-dev/neo-go/pkg/core/mpt.MapToMPTBatch(0xc003304ff0)
github.com/nspcc-dev/neo-go/pkg/core/mpt/batch.go:28 +0x2e7
github.com/nspcc-dev/neo-go/pkg/core.(*Blockchain).storeBlock(0xc000400280, 0xc000f178c0, 0xc005a04370)
github.com/nspcc-dev/neo-go/pkg/core/blockchain.go:1136 +0xfef
github.com/nspcc-dev/neo-go/pkg/core.(*Blockchain).AddBlock(0xc000400280, 0xc000f178c0)
github.com/nspcc-dev/neo-go/pkg/core/blockchain.go:893 +0x755
github.com/nspcc-dev/neo-go/pkg/core/chaindump.Restore({0x11c3528, 0xc000400280}, 0xc350, 0x0, 0x4c221, 0xc000458710)
github.com/nspcc-dev/neo-go/pkg/core/chaindump/dump.go:73 +0x2ca
github.com/nspcc-dev/neo-go/cli/server.restoreDB(0xc000410000)
github.com/nspcc-dev/neo-go/cli/server/server.go:381 +0xcfa
github.com/urfave/cli.HandleAction({0xdbbfa0, 0x10a1078}, 0x7)
github.com/urfave/cli@v1.22.5/app.go:524 +0xa8
github.com/urfave/cli.Command.Run({{0xf32b10, 0x7}, {0x0, 0x0}, {0x0, 0x0, 0x0}, {0xf4a41d, 0x1c}, {0x0, ...}, ...}, ...)
github.com/urfave/cli@v1.22.5/command.go:173 +0x652
github.com/urfave/cli.(*App).RunAsSubcommand(0xc0001f7880, 0xc0001adce0)
github.com/urfave/cli@v1.22.5/app.go:405 +0x9ec
github.com/urfave/cli.Command.startApp({{0xf2e982, 0x2}, {0x0, 0x0}, {0x0, 0x0, 0x0}, {0xf41af9, 0x16}, {0x0, ...}, ...}, ...)
github.com/urfave/cli@v1.22.5/command.go:372 +0x6e9
github.com/urfave/cli.Command.Run({{0xf2e982, 0x2}, {0x0, 0x0}, {0x0, 0x0, 0x0}, {0xf41af9, 0x16}, {0x0, ...}, ...}, ...)
github.com/urfave/cli@v1.22.5/command.go:102 +0x808
github.com/urfave/cli.(*App).Run(0xc0001f76c0, {0xc00012e000, 0x8, 0x8})
github.com/urfave/cli@v1.22.5/app.go:277 +0x705
main.main()
./main.go:19 +0x33
goroutine 66 [select]:
github.com/syndtr/goleveldb/leveldb/util.(*BufferPool).drain(0xc00027e1c0)
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/util/buffer_pool.go:209 +0xcd
created by github.com/syndtr/goleveldb/leveldb/util.NewBufferPool
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/util/buffer_pool.go:240 +0x19b
goroutine 67 [select]:
github.com/syndtr/goleveldb/leveldb.(*session).refLoop(0xc0002441e0)
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/session_util.go:189 +0x5bb
created by github.com/syndtr/goleveldb/leveldb.newSession
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/session.go:93 +0x2de
goroutine 85 [select]:
github.com/syndtr/goleveldb/leveldb.(*DB).compactionError(0xc0001f7a40)
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/db_compaction.go:91 +0x15e
created by github.com/syndtr/goleveldb/leveldb.openDB
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/db.go:148 +0x4ef
goroutine 86 [select]:
github.com/syndtr/goleveldb/leveldb.(*DB).mpoolDrain(0xc0001f7a40)
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/db_state.go:101 +0xae
created by github.com/syndtr/goleveldb/leveldb.openDB
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/db.go:149 +0x531
goroutine 87 [runnable]:
github.com/syndtr/goleveldb/leveldb.(*compaction).shouldStopBefore(0xc001787440, {0xc001890720, 0x29, 0x30})
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/session_compaction.go:272 +0x10a
github.com/syndtr/goleveldb/leveldb.(*tableCompactionBuilder).run(0xc0001ca140, 0xc002039638)
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/db_compaction.go:461 +0x565
github.com/syndtr/goleveldb/leveldb.(*DB).compactionTransact(0xc0001f7a40, {0xf369f0, 0xb}, {0x11b9b60, 0xc0001ca140})
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/db_compaction.go:186 +0x22f
github.com/syndtr/goleveldb/leveldb.(*DB).tableCompaction(0xc0001f7a40, 0xc001787440, 0x0)
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/db_compaction.go:580 +0xa30
github.com/syndtr/goleveldb/leveldb.(*DB).tableAutoCompaction(0xc0001f7a40)
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/db_compaction.go:644 +0x39
github.com/syndtr/goleveldb/leveldb.(*DB).tCompaction(0xc0001f7a40)
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/db_compaction.go:863 +0x43b
created by github.com/syndtr/goleveldb/leveldb.openDB
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/db.go:155 +0x5a7
goroutine 88 [select]:
github.com/syndtr/goleveldb/leveldb.(*DB).mCompaction(0xc0001f7a40)
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/db_compaction.go:773 +0x119
created by github.com/syndtr/goleveldb/leveldb.openDB
github.com/syndtr/goleveldb@v1.0.1-0.20210305035536-64b5b1c73954/leveldb/db.go:156 +0x5e9
goroutine 78 [select]:
github.com/nspcc-dev/neo-go/pkg/core.(*Blockchain).Run(0xc000400280)
github.com/nspcc-dev/neo-go/pkg/core/blockchain.go:615 +0x185
created by github.com/nspcc-dev/neo-go/cli/server.initBCWithMetrics
github.com/nspcc-dev/neo-go/cli/server/server.go:217 +0x23d
goroutine 79 [IO wait]:
internal/poll.runtime_pollWait(0x7ffb00b2aad8, 0x72)
runtime/netpoll.go:229 +0x89
internal/poll.(*pollDesc).wait(0xc00012e180, 0x4, 0x0)
internal/poll/fd_poll_runtime.go:84 +0x32
internal/poll.(*pollDesc).waitRead(...)
internal/poll/fd_poll_runtime.go:89
internal/poll.(*FD).Accept(0xc00012e180)
internal/poll/fd_unix.go:402 +0x22c
net.(*netFD).accept(0xc00012e180)
net/fd_unix.go:173 +0x35
net.(*TCPListener).accept(0xc000280090)
net/tcpsock_posix.go:140 +0x28
net.(*TCPListener).Accept(0xc000280090)
net/tcpsock.go:262 +0x3d
net/http.(*Server).Serve(0xc00027e380, {0x11bd4a8, 0xc000280090})
net/http/server.go:3001 +0x394
net/http.(*Server).ListenAndServe(0xc00027e380)
net/http/server.go:2930 +0x7d
github.com/nspcc-dev/neo-go/pkg/network/metrics.(*Service).Start(0xc000097d10)
github.com/nspcc-dev/neo-go/pkg/network/metrics/metrics.go:29 +0x115
created by github.com/nspcc-dev/neo-go/cli/server.initBCWithMetrics
github.com/nspcc-dev/neo-go/cli/server/server.go:218 +0x285
goroutine 9 [syscall]:
os/signal.signal_recv()
runtime/sigqueue.go:169 +0x98
os/signal.loop()
os/signal/signal_unix.go:24 +0x19
created by os/signal.Notify.func1.1
os/signal/signal.go:151 +0x2c
goroutine 8 [select]:
github.com/nspcc-dev/neo-go/pkg/core.(*Blockchain).notificationDispatcher(0xc000400280)
github.com/nspcc-dev/neo-go/pkg/core/blockchain.go:739 +0x2e7
created by github.com/nspcc-dev/neo-go/pkg/core.(*Blockchain).Run
github.com/nspcc-dev/neo-go/pkg/core/blockchain.go:612 +0xe5
goroutine 10 [chan receive]:
github.com/nspcc-dev/neo-go/cli/server.newGraceContext.func1()
github.com/nspcc-dev/neo-go/cli/server/server.go:117 +0x28
created by github.com/nspcc-dev/neo-go/cli/server.newGraceContext
github.com/nspcc-dev/neo-go/cli/server/server.go:116 +0xde
```
2022-05-17 05:59:48 +00:00
|
|
|
}
|
|
|
|
})
|
2022-05-04 14:00:18 +00:00
|
|
|
return stackitem.NewInterop(item)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *NEO) getCandidateVoteCall(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
|
|
pub := toPublicKey(args[0])
|
|
|
|
key := makeValidatorKey(pub)
|
|
|
|
si := ic.DAO.GetStorageItem(n.ID, key)
|
|
|
|
if si == nil {
|
|
|
|
return stackitem.NewBigInteger(big.NewInt(-1))
|
|
|
|
}
|
|
|
|
c := new(candidate).FromBytes(si)
|
|
|
|
if !c.Registered {
|
|
|
|
return stackitem.NewBigInteger(big.NewInt(-1))
|
|
|
|
}
|
|
|
|
return stackitem.NewBigInteger(&c.Votes)
|
|
|
|
}
|
|
|
|
|
2021-05-25 14:54:57 +00:00
|
|
|
func (n *NEO) getAccountState(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
|
|
key := makeAccountKey(toUint160(args[0]))
|
|
|
|
si := ic.DAO.GetStorageItem(n.ID, key)
|
|
|
|
if len(si) == 0 {
|
|
|
|
return stackitem.Null{}
|
|
|
|
}
|
|
|
|
|
2021-07-16 13:00:25 +00:00
|
|
|
item, err := stackitem.Deserialize(si)
|
|
|
|
if err != nil {
|
|
|
|
panic(err) // no errors are expected but we better be sure
|
2021-05-25 14:54:57 +00:00
|
|
|
}
|
|
|
|
return item
|
|
|
|
}
|
|
|
|
|
native: rework native NEO next block validators cache
Blockchain passes his own pure unwrapped DAO to
(*Blockchain).ComputeNextBlockValidators which means that native
RW NEO cache structure stored inside this DAO can be modified by
anyone who uses exported ComputeNextBlockValidators Blockchain API,
and technically it's valid, and we should allow this, because it's
the only purpose of `validators` caching. However, at the same time
some RPC server is allowed to request a subsequent wrapped DAO for
some test invocation. It means that descendant wrapped DAO
eventually will request RW NEO cache and try to `Copy()`
the underlying's DAO cache which is in direct use of
ComputeNextBlockValidators. Here's the race:
ComputeNextBlockValidators called by Consensus service tries to
update cached `validators` value, and descendant wrapped DAO
created by the RPC server tries to copy DAO's native cache and
read the cached `validators` value.
So the problem is that native cache not designated to handle
concurrent access between parent DAO layer and derived (wrapped)
DAO layer. I've carefully reviewed all the usages of native cache,
and turns out that the described situation is the only place where
parent DAO is used directly to modify its cache concurrently with
some descendant DAO that is trying to access the cache. All other
usages of native cache (not only NEO, but also all other native
contrcts) strictly rely on the hierarchical DAO structure and don't
try to perform these concurrent operations between DAO layers.
There's also persist operation, but it keeps cache RW lock taken,
so it doesn't have this problem as far. Thus, in this commit we rework
NEO's `validators` cache value so that it always contain the relevant
list for upper Blockchain's DAO and is updated every PostPersist (if
needed).
Note: we must be very careful extending our native cache in the
future, every usage of native cache must be checked against the
described problem.
Close #2989.
Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
2023-08-30 11:02:42 +00:00
|
|
|
// ComputeNextBlockValidators computes an actual list of current validators that is
|
2023-09-01 12:10:42 +00:00
|
|
|
// relevant for the latest processed dBFT epoch and based on the changes made by
|
|
|
|
// register/unregister/vote calls during the latest epoch.
|
native: rework native NEO next block validators cache
Blockchain passes his own pure unwrapped DAO to
(*Blockchain).ComputeNextBlockValidators which means that native
RW NEO cache structure stored inside this DAO can be modified by
anyone who uses exported ComputeNextBlockValidators Blockchain API,
and technically it's valid, and we should allow this, because it's
the only purpose of `validators` caching. However, at the same time
some RPC server is allowed to request a subsequent wrapped DAO for
some test invocation. It means that descendant wrapped DAO
eventually will request RW NEO cache and try to `Copy()`
the underlying's DAO cache which is in direct use of
ComputeNextBlockValidators. Here's the race:
ComputeNextBlockValidators called by Consensus service tries to
update cached `validators` value, and descendant wrapped DAO
created by the RPC server tries to copy DAO's native cache and
read the cached `validators` value.
So the problem is that native cache not designated to handle
concurrent access between parent DAO layer and derived (wrapped)
DAO layer. I've carefully reviewed all the usages of native cache,
and turns out that the described situation is the only place where
parent DAO is used directly to modify its cache concurrently with
some descendant DAO that is trying to access the cache. All other
usages of native cache (not only NEO, but also all other native
contrcts) strictly rely on the hierarchical DAO structure and don't
try to perform these concurrent operations between DAO layers.
There's also persist operation, but it keeps cache RW lock taken,
so it doesn't have this problem as far. Thus, in this commit we rework
NEO's `validators` cache value so that it always contain the relevant
list for upper Blockchain's DAO and is updated every PostPersist (if
needed).
Note: we must be very careful extending our native cache in the
future, every usage of native cache must be checked against the
described problem.
Close #2989.
Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
2023-08-30 11:02:42 +00:00
|
|
|
// Note: this method isn't actually "computes" new committee list and calculates
|
2023-09-01 12:10:42 +00:00
|
|
|
// new validators list from it. Instead, it uses cache, and the cache itself is
|
|
|
|
// updated during the PostPersist of the last block of every epoch.
|
native: rework native NEO next block validators cache
Blockchain passes his own pure unwrapped DAO to
(*Blockchain).ComputeNextBlockValidators which means that native
RW NEO cache structure stored inside this DAO can be modified by
anyone who uses exported ComputeNextBlockValidators Blockchain API,
and technically it's valid, and we should allow this, because it's
the only purpose of `validators` caching. However, at the same time
some RPC server is allowed to request a subsequent wrapped DAO for
some test invocation. It means that descendant wrapped DAO
eventually will request RW NEO cache and try to `Copy()`
the underlying's DAO cache which is in direct use of
ComputeNextBlockValidators. Here's the race:
ComputeNextBlockValidators called by Consensus service tries to
update cached `validators` value, and descendant wrapped DAO
created by the RPC server tries to copy DAO's native cache and
read the cached `validators` value.
So the problem is that native cache not designated to handle
concurrent access between parent DAO layer and derived (wrapped)
DAO layer. I've carefully reviewed all the usages of native cache,
and turns out that the described situation is the only place where
parent DAO is used directly to modify its cache concurrently with
some descendant DAO that is trying to access the cache. All other
usages of native cache (not only NEO, but also all other native
contrcts) strictly rely on the hierarchical DAO structure and don't
try to perform these concurrent operations between DAO layers.
There's also persist operation, but it keeps cache RW lock taken,
so it doesn't have this problem as far. Thus, in this commit we rework
NEO's `validators` cache value so that it always contain the relevant
list for upper Blockchain's DAO and is updated every PostPersist (if
needed).
Note: we must be very careful extending our native cache in the
future, every usage of native cache must be checked against the
described problem.
Close #2989.
Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
2023-08-30 11:02:42 +00:00
|
|
|
func (n *NEO) ComputeNextBlockValidators(d *dao.Simple) keys.PublicKeys {
|
|
|
|
// It should always be OK with RO cache if using lower-layered DAO with proper
|
|
|
|
// cache set.
|
2022-04-28 15:38:13 +00:00
|
|
|
cache := d.GetROCache(n.ID).(*NeoCache)
|
2023-09-01 12:33:01 +00:00
|
|
|
if vals := cache.newEpochNextValidators; vals != nil {
|
native: rework native NEO next block validators cache
Blockchain passes his own pure unwrapped DAO to
(*Blockchain).ComputeNextBlockValidators which means that native
RW NEO cache structure stored inside this DAO can be modified by
anyone who uses exported ComputeNextBlockValidators Blockchain API,
and technically it's valid, and we should allow this, because it's
the only purpose of `validators` caching. However, at the same time
some RPC server is allowed to request a subsequent wrapped DAO for
some test invocation. It means that descendant wrapped DAO
eventually will request RW NEO cache and try to `Copy()`
the underlying's DAO cache which is in direct use of
ComputeNextBlockValidators. Here's the race:
ComputeNextBlockValidators called by Consensus service tries to
update cached `validators` value, and descendant wrapped DAO
created by the RPC server tries to copy DAO's native cache and
read the cached `validators` value.
So the problem is that native cache not designated to handle
concurrent access between parent DAO layer and derived (wrapped)
DAO layer. I've carefully reviewed all the usages of native cache,
and turns out that the described situation is the only place where
parent DAO is used directly to modify its cache concurrently with
some descendant DAO that is trying to access the cache. All other
usages of native cache (not only NEO, but also all other native
contrcts) strictly rely on the hierarchical DAO structure and don't
try to perform these concurrent operations between DAO layers.
There's also persist operation, but it keeps cache RW lock taken,
so it doesn't have this problem as far. Thus, in this commit we rework
NEO's `validators` cache value so that it always contain the relevant
list for upper Blockchain's DAO and is updated every PostPersist (if
needed).
Note: we must be very careful extending our native cache in the
future, every usage of native cache must be checked against the
described problem.
Close #2989.
Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
2023-08-30 11:02:42 +00:00
|
|
|
return vals.Copy()
|
2020-06-24 10:20:59 +00:00
|
|
|
}
|
2023-09-01 12:10:42 +00:00
|
|
|
// It's a caller's program error to call ComputeNextBlockValidators not having
|
2023-09-01 15:15:36 +00:00
|
|
|
// the right value in lower cache. With the current scheme of handling
|
|
|
|
// newEpochNextValidators cache this code is expected to be unreachable, but
|
|
|
|
// let's have this panic here just in case.
|
2023-09-01 12:33:01 +00:00
|
|
|
panic("bug: unexpected external call to newEpochNextValidators cache")
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
2020-08-03 12:00:27 +00:00
|
|
|
func (n *NEO) getCommittee(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
2022-04-12 14:29:11 +00:00
|
|
|
pubs := n.GetCommitteeMembers(ic.DAO)
|
2020-08-03 12:00:27 +00:00
|
|
|
sort.Sort(pubs)
|
|
|
|
return pubsToArray(pubs)
|
|
|
|
}
|
|
|
|
|
2022-02-16 15:04:47 +00:00
|
|
|
func (n *NEO) modifyVoterTurnout(d *dao.Simple, amount *big.Int) error {
|
2020-08-03 12:00:27 +00:00
|
|
|
key := []byte{prefixVotersCount}
|
2021-02-09 09:26:25 +00:00
|
|
|
si := d.GetStorageItem(n.ID, key)
|
2020-08-03 12:00:27 +00:00
|
|
|
if si == nil {
|
|
|
|
return errors.New("voters count not found")
|
|
|
|
}
|
2021-03-05 14:06:54 +00:00
|
|
|
votersCount := bigint.FromBytes(si)
|
2020-08-03 12:00:27 +00:00
|
|
|
votersCount.Add(votersCount, amount)
|
2022-05-31 20:10:56 +00:00
|
|
|
d.PutBigInt(n.ID, key, votersCount)
|
2022-02-16 14:48:15 +00:00
|
|
|
return nil
|
2020-08-03 12:00:27 +00:00
|
|
|
}
|
|
|
|
|
2020-08-28 07:24:54 +00:00
|
|
|
// GetCommitteeMembers returns public keys of nodes in committee using cached value.
|
2022-04-12 14:29:11 +00:00
|
|
|
func (n *NEO) GetCommitteeMembers(d *dao.Simple) keys.PublicKeys {
|
2022-04-20 14:47:48 +00:00
|
|
|
cache := d.GetROCache(n.ID).(*NeoCache)
|
2023-09-01 13:00:53 +00:00
|
|
|
return getCommitteeMembers(cache.committee)
|
2022-04-12 14:29:11 +00:00
|
|
|
}
|
|
|
|
|
2023-09-01 13:00:53 +00:00
|
|
|
func getCommitteeMembers(cvs keysWithVotes) keys.PublicKeys {
|
2020-11-05 07:43:43 +00:00
|
|
|
var committee = make(keys.PublicKeys, len(cvs))
|
|
|
|
var err error
|
|
|
|
for i := range committee {
|
|
|
|
committee[i], err = cvs[i].PublicKey()
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return committee
|
2020-08-28 07:24:54 +00:00
|
|
|
}
|
|
|
|
|
2020-11-05 07:43:43 +00:00
|
|
|
func toKeysWithVotes(pubs keys.PublicKeys) keysWithVotes {
|
|
|
|
ks := make(keysWithVotes, len(pubs))
|
|
|
|
for i := range pubs {
|
|
|
|
ks[i].UnmarshaledKey = pubs[i]
|
|
|
|
ks[i].Key = string(pubs[i].Bytes())
|
|
|
|
ks[i].Votes = big.NewInt(0)
|
|
|
|
}
|
|
|
|
return ks
|
|
|
|
}
|
|
|
|
|
|
|
|
// computeCommitteeMembers returns public keys of nodes in committee.
|
2022-04-29 15:00:46 +00:00
|
|
|
func (n *NEO) computeCommitteeMembers(blockHeight uint32, d *dao.Simple) (keys.PublicKeys, keysWithVotes, error) {
|
2020-08-03 12:00:27 +00:00
|
|
|
key := []byte{prefixVotersCount}
|
2021-02-09 09:26:25 +00:00
|
|
|
si := d.GetStorageItem(n.ID, key)
|
2020-08-03 12:00:27 +00:00
|
|
|
if si == nil {
|
2020-11-05 07:43:43 +00:00
|
|
|
return nil, nil, errors.New("voters count not found")
|
2020-08-03 12:00:27 +00:00
|
|
|
}
|
2021-03-05 14:06:54 +00:00
|
|
|
votersCount := bigint.FromBytes(si)
|
2020-08-03 12:00:27 +00:00
|
|
|
// votersCount / totalSupply must be >= 0.2
|
2021-11-30 18:10:48 +00:00
|
|
|
votersCount.Mul(votersCount, bigEffectiveVoterTurnout)
|
2021-08-02 21:19:23 +00:00
|
|
|
_, totalSupply := n.getTotalSupply(d)
|
|
|
|
voterTurnout := votersCount.Div(votersCount, totalSupply)
|
2020-12-23 12:37:56 +00:00
|
|
|
|
2022-04-29 15:00:46 +00:00
|
|
|
count := n.cfg.GetCommitteeSize(blockHeight + 1)
|
2022-01-24 15:36:31 +00:00
|
|
|
// Can be sorted and/or returned to outside users, thus needs to be copied.
|
|
|
|
sbVals := keys.PublicKeys(n.standbyKeys[:count]).Copy()
|
2022-05-04 14:00:18 +00:00
|
|
|
cs, err := n.getCandidates(d, false, -1)
|
2020-08-03 12:00:27 +00:00
|
|
|
if err != nil {
|
2020-11-05 07:43:43 +00:00
|
|
|
return nil, nil, err
|
2020-08-03 12:00:27 +00:00
|
|
|
}
|
2020-12-23 12:37:56 +00:00
|
|
|
if voterTurnout.Sign() != 1 || len(cs) < count {
|
|
|
|
kvs := make(keysWithVotes, count)
|
|
|
|
for i := range kvs {
|
|
|
|
kvs[i].UnmarshaledKey = sbVals[i]
|
|
|
|
kvs[i].Key = string(sbVals[i].Bytes())
|
|
|
|
votes := big.NewInt(0)
|
|
|
|
for j := range cs {
|
|
|
|
if cs[j].Key == kvs[i].Key {
|
|
|
|
votes = cs[j].Votes
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
kvs[i].Votes = votes
|
|
|
|
}
|
|
|
|
return sbVals, kvs, nil
|
2020-08-03 12:00:27 +00:00
|
|
|
}
|
|
|
|
pubs := make(keys.PublicKeys, count)
|
|
|
|
for i := range pubs {
|
2020-11-05 07:43:43 +00:00
|
|
|
pubs[i], err = cs[i].PublicKey()
|
2020-08-03 12:00:27 +00:00
|
|
|
if err != nil {
|
2020-11-05 07:43:43 +00:00
|
|
|
return nil, nil, err
|
2020-08-03 12:00:27 +00:00
|
|
|
}
|
|
|
|
}
|
2020-11-05 07:43:43 +00:00
|
|
|
return pubs, cs[:count], nil
|
2020-08-03 12:00:27 +00:00
|
|
|
}
|
|
|
|
|
2020-06-03 12:55:06 +00:00
|
|
|
func (n *NEO) getNextBlockValidators(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
2022-04-12 14:29:11 +00:00
|
|
|
result := n.GetNextBlockValidatorsInternal(ic.DAO)
|
2020-03-25 10:00:11 +00:00
|
|
|
return pubsToArray(result)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetNextBlockValidatorsInternal returns next block validators.
|
2022-04-12 14:29:11 +00:00
|
|
|
func (n *NEO) GetNextBlockValidatorsInternal(d *dao.Simple) keys.PublicKeys {
|
2022-04-20 14:47:48 +00:00
|
|
|
cache := d.GetROCache(n.ID).(*NeoCache)
|
2022-04-19 09:26:46 +00:00
|
|
|
return cache.nextValidators.Copy()
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
2021-07-25 12:00:44 +00:00
|
|
|
// BalanceOf returns native NEO token balance for the acc.
|
2022-02-16 15:04:47 +00:00
|
|
|
func (n *NEO) BalanceOf(d *dao.Simple, acc util.Uint160) (*big.Int, uint32) {
|
2021-07-25 12:00:44 +00:00
|
|
|
key := makeAccountKey(acc)
|
|
|
|
si := d.GetStorageItem(n.ID, key)
|
|
|
|
if si == nil {
|
|
|
|
return big.NewInt(0), 0
|
|
|
|
}
|
|
|
|
st, err := state.NEOBalanceFromBytes(si)
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Errorf("failed to decode NEO balance state: %w", err))
|
|
|
|
}
|
|
|
|
return &st.Balance, st.BalanceHeight
|
|
|
|
}
|
|
|
|
|
2020-06-03 12:55:06 +00:00
|
|
|
func pubsToArray(pubs keys.PublicKeys) stackitem.Item {
|
|
|
|
arr := make([]stackitem.Item, len(pubs))
|
2020-03-25 10:00:11 +00:00
|
|
|
for i := range pubs {
|
2020-06-03 12:55:06 +00:00
|
|
|
arr[i] = stackitem.NewByteArray(pubs[i].Bytes())
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
2020-06-03 12:55:06 +00:00
|
|
|
return stackitem.NewArray(arr)
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
2020-06-03 12:55:06 +00:00
|
|
|
func toPublicKey(s stackitem.Item) *keys.PublicKey {
|
2020-03-25 10:00:11 +00:00
|
|
|
buf, err := s.TryBytes()
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
pub := new(keys.PublicKey)
|
|
|
|
if err := pub.DecodeBytes(buf); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return pub
|
|
|
|
}
|
2020-10-21 14:28:45 +00:00
|
|
|
|
|
|
|
// putGASRecord is a helper which creates key and puts GASPerBlock value into the storage.
|
2022-02-16 15:04:47 +00:00
|
|
|
func (n *NEO) putGASRecord(dao *dao.Simple, index uint32, value *big.Int) {
|
2020-10-21 14:28:45 +00:00
|
|
|
key := make([]byte, 5)
|
|
|
|
key[0] = prefixGASPerBlock
|
|
|
|
binary.BigEndian.PutUint32(key[1:], index)
|
2022-05-31 20:10:56 +00:00
|
|
|
dao.PutBigInt(n.ID, key, value)
|
2020-10-21 14:28:45 +00:00
|
|
|
}
|