Merge pull request #682 from nspcc-dev/consensus-fixes

Consensus fixes
This commit is contained in:
Roman Khimov 2020-02-19 12:47:30 +03:00 committed by GitHub
commit 6dc9023289
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 323 additions and 125 deletions

View file

@ -46,7 +46,7 @@ type (
AddressVersion byte `yaml:"AddressVersion"`
SecondsPerBlock int `yaml:"SecondsPerBlock"`
LowPriorityThreshold float64 `yaml:"LowPriorityThreshold"`
MaxTransactionsPerBlock int64 `yaml:"MaxTransactionsPerBlock"`
MaxTransactionsPerBlock int `yaml:"MaxTransactionsPerBlock"`
MemPoolSize int `yaml:"MemPoolSize"`
StandbyValidators []string `yaml:"StandbyValidators"`
SeedList []string `yaml:"SeedList"`
@ -59,6 +59,13 @@ type (
FreeGasLimit util.Fixed8 `yaml:"FreeGasLimit"`
// SaveStorageBatch enables storage batch saving before every persist.
SaveStorageBatch bool `yaml:"SaveStorageBatch"`
// Maximum number of low priority transactions accepted into block.
MaxFreeTransactionsPerBlock int `yaml:"MaxFreeTransactionsPerBlock"`
// Maximum size of low priority transaction in bytes.
MaxFreeTransactionSize int `yaml:"MaxFreeTransactionSize"`
// FeePerExtraByte sets the expected per-byte fee for
// transactions exceeding the MaxFreeTransactionSize.
FeePerExtraByte float64 `yaml:"FeePerExtraByte"`
}
// SystemFee fees related to system.

View file

@ -31,6 +31,10 @@ ProtocolConfiguration:
VerifyBlocks: true
VerifyTransactions: false
FreeGasLimit: 10.0
MaxTransactionsPerBlock: 500
MaxFreeTransactionsPerBlock: 20
MaxFreeTransactionSize: 1024
FeePerExtraByte: 0.00001
ApplicationConfiguration:
# LogPath could be set up in case you need stdout logs to some proper file.

View file

@ -31,6 +31,10 @@ ProtocolConfiguration:
VerifyBlocks: true
VerifyTransactions: false
FreeGasLimit: 10.0
MaxTransactionsPerBlock: 500
MaxFreeTransactionsPerBlock: 20
MaxFreeTransactionSize: 1024
FeePerExtraByte: 0.00001
ApplicationConfiguration:
# LogPath could be set up in case you need stdout logs to some proper file.

2
go.mod
View file

@ -7,7 +7,7 @@ require (
github.com/go-redis/redis v6.10.2+incompatible
github.com/go-yaml/yaml v2.1.0+incompatible
github.com/mr-tron/base58 v1.1.2
github.com/nspcc-dev/dbft v0.0.0-20200211143830-4deeb124d7f9
github.com/nspcc-dev/dbft v0.0.0-20200218131838-be55fd41ea78
github.com/nspcc-dev/rfc6979 v0.2.0
github.com/pkg/errors v0.8.1
github.com/prometheus/client_golang v1.2.1

4
go.sum
View file

@ -97,8 +97,8 @@ github.com/nspcc-dev/dbft v0.0.0-20191209120240-0d6b7568d9ae h1:T5V1QANlNMKun0EP
github.com/nspcc-dev/dbft v0.0.0-20191209120240-0d6b7568d9ae/go.mod h1:3FjXOoHmA51EGfb5GS/HOv7VdmngNRTssSeQ729dvGY=
github.com/nspcc-dev/dbft v0.0.0-20200117124306-478e5cfbf03a h1:ajvxgEe9qY4vvoSmrADqdDx7hReodKTnT2IXN++qZG8=
github.com/nspcc-dev/dbft v0.0.0-20200117124306-478e5cfbf03a/go.mod h1:/YFK+XOxxg0Bfm6P92lY5eDSLYfp06XOdL8KAVgXjVk=
github.com/nspcc-dev/dbft v0.0.0-20200211143830-4deeb124d7f9 h1:P3uOj+M3DkUlwGEFHbmwRHUZdpCuFzfM30j6iBjrPbY=
github.com/nspcc-dev/dbft v0.0.0-20200211143830-4deeb124d7f9/go.mod h1:O0qtn62prQSqizzoagHmuuKoz8QMkU3SzBoKdEvm3aQ=
github.com/nspcc-dev/dbft v0.0.0-20200218131838-be55fd41ea78 h1:cb8hWea3yyqbxzmnqqSSgqVCRoKNrFaS99Cqt0Qm7nQ=
github.com/nspcc-dev/dbft v0.0.0-20200218131838-be55fd41ea78/go.mod h1:O0qtn62prQSqizzoagHmuuKoz8QMkU3SzBoKdEvm3aQ=
github.com/nspcc-dev/neofs-crypto v0.2.0 h1:ftN+59WqxSWz/RCgXYOfhmltOOqU+udsNQSvN6wkFck=
github.com/nspcc-dev/neofs-crypto v0.2.0/go.mod h1:F/96fUzPM3wR+UGsPi3faVNmFlA9KAEAUQR7dMxZmNA=
github.com/nspcc-dev/neofs-crypto v0.2.3 h1:aca3X2aly92ENRbFK+kH6Hd+J9EQ4Eu6XMVoITSIKtc=

View file

@ -9,8 +9,8 @@ import (
"github.com/CityOfZion/neo-go/config"
"github.com/CityOfZion/neo-go/pkg/core"
coreb "github.com/CityOfZion/neo-go/pkg/core/block"
"github.com/CityOfZion/neo-go/pkg/core/mempool"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/smartcontract"
"github.com/CityOfZion/neo-go/pkg/util"
@ -42,6 +42,9 @@ type Service interface {
OnTransaction(tx *transaction.Transaction)
// GetPayload returns Payload with specified hash if it is present in the local cache.
GetPayload(h util.Uint256) *Payload
// OnNewBlock notifies consensus service that there is a new block in
// the chain (without explicitly passing it to the service).
OnNewBlock()
}
type service struct {
@ -57,6 +60,9 @@ type service struct {
// everything in single thread.
messages chan Payload
transactions chan *transaction.Transaction
// blockEvents is used to pass a new block event to the consensus
// process.
blockEvents chan struct{}
lastProposal []util.Uint256
wallet *wallet.Wallet
}
@ -101,6 +107,7 @@ func NewService(cfg Config) (Service, error) {
messages: make(chan Payload, 100),
transactions: make(chan *transaction.Transaction, 100),
blockEvents: make(chan struct{}, 1),
}
if cfg.Wallet == nil {
@ -168,14 +175,7 @@ func (s *service) eventLoop() {
s.log.Debug("timer fired",
zap.Uint32("height", hv.Height),
zap.Uint("view", uint(hv.View)))
if s.Chain.BlockHeight() >= s.dbft.BlockIndex {
s.log.Debug("chain already advanced",
zap.Uint32("dbft index", s.dbft.BlockIndex),
zap.Uint32("chain index", s.Chain.BlockHeight()))
s.dbft.InitializeConsensus(0)
} else {
s.dbft.OnTimeout(hv)
}
s.dbft.OnTimeout(hv)
case msg := <-s.messages:
fields := []zap.Field{
zap.Uint16("from", msg.validatorIndex),
@ -204,6 +204,11 @@ func (s *service) eventLoop() {
s.dbft.OnReceive(&msg)
case tx := <-s.transactions:
s.dbft.OnTransaction(tx)
case <-s.blockEvents:
s.log.Debug("new block in the chain",
zap.Uint32("dbft index", s.dbft.BlockIndex),
zap.Uint32("chain index", s.Chain.BlockHeight()))
s.dbft.InitializeConsensus(0)
}
}
}
@ -215,16 +220,15 @@ func (s *service) validatePayload(p *Payload) bool {
}
pub := validators[p.validatorIndex]
vs := pub.(*publicKey).GetVerificationScript()
h := hash.Hash160(vs)
h := pub.(*publicKey).GetScriptHash()
return p.Verify(h)
}
func (s *service) getKeyPair(pubs []crypto.PublicKey) (int, crypto.PrivateKey, crypto.PublicKey) {
for i := range pubs {
script := pubs[i].(*publicKey).GetVerificationScript()
acc := s.wallet.GetAccount(hash.Hash160(script))
sh := pubs[i].(*publicKey).GetScriptHash()
acc := s.wallet.GetAccount(sh)
if acc == nil {
continue
}
@ -276,6 +280,20 @@ func (s *service) OnTransaction(tx *transaction.Transaction) {
}
}
// OnNewBlock notifies consensus process that there is a new block in the chain
// and dbft should probably be reinitialized.
func (s *service) OnNewBlock() {
if s.dbft != nil {
// If there is something in the queue already, the second
// consecutive event doesn't make much sense (reinitializing
// dbft twice doesn't improve it in any way).
select {
case s.blockEvents <- struct{}{}:
default:
}
}
}
// GetPayload returns payload stored in cache.
func (s *service) GetPayload(h util.Uint256) *Payload {
p := s.cache.Get(h)
@ -393,13 +411,13 @@ func (s *service) getBlock(h util.Uint256) block.Block {
func (s *service) getVerifiedTx(count int) []block.Transaction {
pool := s.Config.Chain.GetMemPool()
var txx []*transaction.Transaction
var txx []mempool.TxWithFee
if s.dbft.ViewNumber > 0 {
txx = make([]*transaction.Transaction, 0, len(s.lastProposal))
txx = make([]mempool.TxWithFee, 0, len(s.lastProposal))
for i := range s.lastProposal {
if tx, ok := pool.TryGetValue(s.lastProposal[i]); ok {
txx = append(txx, tx)
if tx, fee, ok := pool.TryGetValue(s.lastProposal[i]); ok {
txx = append(txx, mempool.TxWithFee{Tx: tx, Fee: fee})
}
}
@ -410,11 +428,30 @@ func (s *service) getVerifiedTx(count int) []block.Transaction {
txx = pool.GetVerifiedTransactions()
}
res := make([]block.Transaction, len(txx)+1)
for i := range txx {
res[i+1] = txx[i]
if len(txx) > 0 {
txx = s.Config.Chain.ApplyPolicyToTxSet(txx)
}
res := make([]block.Transaction, len(txx)+1)
var netFee util.Fixed8
for i := range txx {
res[i+1] = txx[i].Tx
netFee += txx[i].Fee
}
var txOuts []transaction.Output
if netFee != 0 {
sh := s.wallet.GetChangeAddress()
if sh.Equals(util.Uint160{}) {
pk := s.dbft.Pub.(*publicKey)
sh = pk.GetScriptHash()
}
txOuts = []transaction.Output{transaction.Output{
AssetID: core.UtilityTokenID(),
Amount: netFee,
ScriptHash: sh,
}}
}
for {
nonce := rand.Uint32()
res[0] = &transaction.Transaction{
@ -423,7 +460,7 @@ func (s *service) getVerifiedTx(count int) []block.Transaction {
Data: &transaction.MinerTX{Nonce: nonce},
Attributes: nil,
Inputs: nil,
Outputs: nil,
Outputs: txOuts,
Scripts: nil,
Trimmed: false,
}

View file

@ -239,6 +239,6 @@ func newTestChain(t *testing.T) *core.Blockchain {
type feer struct{}
func (fs *feer) NetworkFee(*transaction.Transaction) util.Fixed8 { return util.Fixed8(0) }
func (fs *feer) IsLowPriority(*transaction.Transaction) bool { return false }
func (fs *feer) IsLowPriority(util.Fixed8) bool { return false }
func (fs *feer) FeePerByte(*transaction.Transaction) util.Fixed8 { return util.Fixed8(0) }
func (fs *feer) SystemFee(*transaction.Transaction) util.Fixed8 { return util.Fixed8(0) }

View file

@ -49,6 +49,9 @@ var (
// ErrOOM is returned when adding transaction to the memory pool because
// it reached its full capacity.
ErrOOM = errors.New("no space left in the memory pool")
// ErrPolicy is returned on attempt to add transaction that doesn't
// comply with node's configured policy into the mempool.
ErrPolicy = errors.New("not allowed by policy")
)
var (
genAmount = []int{8, 7, 6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
@ -125,6 +128,22 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration, log *zap.L
cfg.MemPoolSize = defaultMemPoolSize
log.Info("mempool size is not set or wrong, setting default value", zap.Int("MemPoolSize", cfg.MemPoolSize))
}
if cfg.MaxTransactionsPerBlock <= 0 {
cfg.MaxTransactionsPerBlock = 0
log.Info("MaxTransactionsPerBlock is not set or wrong, setting default value (unlimited)", zap.Int("MaxTransactionsPerBlock", cfg.MaxTransactionsPerBlock))
}
if cfg.MaxFreeTransactionsPerBlock <= 0 {
cfg.MaxFreeTransactionsPerBlock = 0
log.Info("MaxFreeTransactionsPerBlock is not set or wrong, setting default value (unlimited)", zap.Int("MaxFreeTransactionsPerBlock", cfg.MaxFreeTransactionsPerBlock))
}
if cfg.MaxFreeTransactionSize <= 0 {
cfg.MaxFreeTransactionSize = 0
log.Info("MaxFreeTransactionSize is not set or wrong, setting default value (unlimited)", zap.Int("MaxFreeTransactionSize", cfg.MaxFreeTransactionSize))
}
if cfg.FeePerExtraByte <= 0 {
cfg.FeePerExtraByte = 0
log.Info("FeePerExtraByte is not set or wrong, setting default value", zap.Float64("FeePerExtraByte", cfg.FeePerExtraByte))
}
bc := &Blockchain{
config: cfg,
dao: newDao(s),
@ -432,7 +451,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
return err
}
if prevTXOutput.AssetID.Equals(governingTokenTX().Hash()) {
if prevTXOutput.AssetID.Equals(GoverningTokenID()) {
spentCoin := NewSpentCoinState(input.PrevHash, prevTXHeight)
spentCoin.items[input.PrevIndex] = block.Index
if err = cache.PutSpentCoinState(input.PrevHash, spentCoin); err != nil {
@ -648,14 +667,14 @@ func processOutputs(tx *transaction.Transaction, dao *cachedDao) error {
}
func processTXWithValidatorsAdd(output *transaction.Output, account *state.Account, dao *cachedDao) error {
if output.AssetID.Equals(governingTokenTX().Hash()) && len(account.Votes) > 0 {
if output.AssetID.Equals(GoverningTokenID()) && len(account.Votes) > 0 {
return modAccountVotes(account, dao, output.Amount)
}
return nil
}
func processTXWithValidatorsSubtract(output *transaction.Output, account *state.Account, dao *cachedDao) error {
if output.AssetID.Equals(governingTokenTX().Hash()) && len(account.Votes) > 0 {
if output.AssetID.Equals(GoverningTokenID()) && len(account.Votes) > 0 {
return modAccountVotes(account, dao, -output.Amount)
}
return nil
@ -724,7 +743,7 @@ func processAccountStateDescriptor(descriptor *transaction.StateDescriptor, dao
}
if descriptor.Field == "Votes" {
balance := account.GetBalanceValues()[governingTokenTX().Hash()]
balance := account.GetBalanceValues()[GoverningTokenID()]
if err = modAccountVotes(account, dao, -balance); err != nil {
return err
}
@ -814,7 +833,7 @@ func (bc *Blockchain) headerListLen() (n int) {
// GetTransaction returns a TX and its height by the given hash.
func (bc *Blockchain) GetTransaction(hash util.Uint256) (*transaction.Transaction, uint32, error) {
if tx, ok := bc.memPool.TryGetValue(hash); ok {
if tx, _, ok := bc.memPool.TryGetValue(hash); ok {
return tx, 0, nil // the height is not actually defined for memPool transaction. Not sure if zero is a good number in this case.
}
return bc.dao.GetTransaction(hash)
@ -999,14 +1018,14 @@ func (bc *Blockchain) FeePerByte(t *transaction.Transaction) util.Fixed8 {
func (bc *Blockchain) NetworkFee(t *transaction.Transaction) util.Fixed8 {
inputAmount := util.Fixed8FromInt64(0)
for _, txOutput := range bc.References(t) {
if txOutput.AssetID == utilityTokenTX().Hash() {
if txOutput.AssetID == UtilityTokenID() {
inputAmount.Add(txOutput.Amount)
}
}
outputAmount := util.Fixed8FromInt64(0)
for _, txOutput := range t.Outputs {
if txOutput.AssetID == utilityTokenTX().Hash() {
if txOutput.AssetID == UtilityTokenID() {
outputAmount.Add(txOutput.Amount)
}
}
@ -1019,10 +1038,10 @@ func (bc *Blockchain) SystemFee(t *transaction.Transaction) util.Fixed8 {
return bc.GetConfig().SystemFee.TryGetValue(t.Type)
}
// IsLowPriority flags a transaction as low priority if the network fee is less than
// IsLowPriority checks given fee for being less than configured
// LowPriorityThreshold.
func (bc *Blockchain) IsLowPriority(t *transaction.Transaction) bool {
return bc.NetworkFee(t) < util.Fixed8FromFloat(bc.GetConfig().LowPriorityThreshold)
func (bc *Blockchain) IsLowPriority(fee util.Fixed8) bool {
return fee < util.Fixed8FromFloat(bc.GetConfig().LowPriorityThreshold)
}
// GetMemPool returns the memory pool of the blockchain.
@ -1030,6 +1049,24 @@ func (bc *Blockchain) GetMemPool() *mempool.Pool {
return &bc.memPool
}
// ApplyPolicyToTxSet applies configured policies to given transaction set. It
// expects slice to be ordered by fee and returns a subslice of it.
func (bc *Blockchain) ApplyPolicyToTxSet(txes []mempool.TxWithFee) []mempool.TxWithFee {
if bc.config.MaxTransactionsPerBlock != 0 && len(txes) > bc.config.MaxTransactionsPerBlock {
txes = txes[:bc.config.MaxTransactionsPerBlock]
}
maxFree := bc.config.MaxFreeTransactionsPerBlock
if maxFree != 0 {
lowStart := sort.Search(len(txes), func(i int) bool {
return bc.IsLowPriority(txes[i].Fee)
})
if lowStart+maxFree < len(txes) {
txes = txes[:lowStart+maxFree]
}
}
return txes
}
// VerifyBlock verifies block against its current state.
func (bc *Blockchain) VerifyBlock(block *block.Block) error {
prevHeader, err := bc.GetHeader(block.PrevHash)
@ -1127,6 +1164,18 @@ func (bc *Blockchain) PoolTx(t *transaction.Transaction) error {
if err := bc.verifyTx(t, nil); err != nil {
return err
}
// Policying.
if t.Type != transaction.ClaimType {
txSize := io.GetVarSize(t)
maxFree := bc.config.MaxFreeTransactionSize
if maxFree != 0 && txSize > maxFree {
netFee := bc.NetworkFee(t)
if bc.IsLowPriority(netFee) ||
netFee < util.Fixed8FromFloat(bc.config.FeePerExtraByte)*util.Fixed8(txSize-maxFree) {
return ErrPolicy
}
}
}
if err := bc.memPool.Add(t, bc); err != nil {
switch err {
case mempool.ErrOOM:
@ -1192,7 +1241,7 @@ func (bc *Blockchain) verifyResults(t *transaction.Transaction) error {
if len(resultsDestroy) > 1 {
return errors.New("tx has more than 1 destroy output")
}
if len(resultsDestroy) == 1 && resultsDestroy[0].AssetID != utilityTokenTX().Hash() {
if len(resultsDestroy) == 1 && resultsDestroy[0].AssetID != UtilityTokenID() {
return errors.New("tx destroys non-utility token")
}
sysfee := bc.SystemFee(t)
@ -1208,14 +1257,14 @@ func (bc *Blockchain) verifyResults(t *transaction.Transaction) error {
switch t.Type {
case transaction.MinerType, transaction.ClaimType:
for _, r := range resultsIssue {
if r.AssetID != utilityTokenTX().Hash() {
if r.AssetID != UtilityTokenID() {
return errors.New("miner or claim tx issues non-utility tokens")
}
}
break
case transaction.IssueType:
for _, r := range resultsIssue {
if r.AssetID == utilityTokenTX().Hash() {
if r.AssetID == UtilityTokenID() {
return errors.New("issue tx issues utility tokens")
}
}
@ -1409,20 +1458,20 @@ func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.P
}
uniqueSBValidators := standByValidators.Unique()
pubKeys := keys.PublicKeys{}
result := keys.PublicKeys{}
for _, validator := range validators {
if validator.RegisteredAndHasVotes() || uniqueSBValidators.Contains(validator.PublicKey) {
pubKeys = append(pubKeys, validator.PublicKey)
result = append(result, validator.PublicKey)
}
}
if pubKeys.Len() >= count {
return pubKeys[:count], nil
}
result := pubKeys.Unique()
for i := 0; i < uniqueSBValidators.Len() && result.Len() < count; i++ {
if !result.Contains(uniqueSBValidators[i]) {
result = append(result, uniqueSBValidators[i])
if result.Len() >= count {
result = result[:count]
} else {
for i := 0; i < uniqueSBValidators.Len() && result.Len() < count; i++ {
if !result.Contains(uniqueSBValidators[i]) {
result = append(result, uniqueSBValidators[i])
}
}
}
sort.Sort(result)

View file

@ -15,6 +15,7 @@ import (
// Blockchainer is an interface that abstract the implementation
// of the blockchain.
type Blockchainer interface {
ApplyPolicyToTxSet([]mempool.TxWithFee) []mempool.TxWithFee
GetConfig() config.ProtocolConfiguration
AddHeaders(...*block.Header) error
AddBlock(*block.Block) error

View file

@ -8,7 +8,6 @@ import (
"github.com/CityOfZion/neo-go/pkg/core/block"
"github.com/CityOfZion/neo-go/pkg/core/state"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/smartcontract/trigger"
"github.com/CityOfZion/neo-go/pkg/util"
@ -311,7 +310,7 @@ func (ic *interopContext) checkHashedWitness(hash util.Uint160) (bool, error) {
// checkKeyedWitness checks hash of signature check contract with a given public
// key against current list of script hashes for verifying in the interop context.
func (ic *interopContext) checkKeyedWitness(key *keys.PublicKey) (bool, error) {
return ic.checkHashedWitness(hash.Hash160(key.GetVerificationScript()))
return ic.checkHashedWitness(key.GetScriptHash())
}
// runtimeCheckWitness checks witnesses.

View file

@ -8,7 +8,7 @@ import (
// Feer is an interface that abstract the implementation of the fee calculation.
type Feer interface {
NetworkFee(t *transaction.Transaction) util.Fixed8
IsLowPriority(t *transaction.Transaction) bool
IsLowPriority(util.Fixed8) bool
FeePerByte(t *transaction.Transaction) util.Fixed8
SystemFee(t *transaction.Transaction) util.Fixed8
}

View file

@ -35,6 +35,12 @@ type item struct {
// items is a slice of item.
type items []*item
// TxWithFee combines transaction and its precalculated network fee.
type TxWithFee struct {
Tx *transaction.Transaction
Fee util.Fixed8
}
// Pool stores the unconfirms transactions.
type Pool struct {
lock sync.RWMutex
@ -128,8 +134,8 @@ func (mp *Pool) Add(t *transaction.Transaction, fee Feer) error {
timeStamp: time.Now().UTC(),
perByteFee: fee.FeePerByte(t),
netFee: fee.NetworkFee(t),
isLowPrio: fee.IsLowPriority(t),
}
pItem.isLowPrio = fee.IsLowPriority(pItem.netFee)
mp.lock.Lock()
if !mp.verifyInputs(t) {
mp.lock.Unlock()
@ -224,29 +230,28 @@ func NewMemPool(capacity int) Pool {
}
}
// TryGetValue returns a transaction if it exists in the memory pool.
func (mp *Pool) TryGetValue(hash util.Uint256) (*transaction.Transaction, bool) {
// TryGetValue returns a transaction and its fee if it exists in the memory pool.
func (mp *Pool) TryGetValue(hash util.Uint256) (*transaction.Transaction, util.Fixed8, bool) {
mp.lock.RLock()
defer mp.lock.RUnlock()
if pItem, ok := mp.verifiedMap[hash]; ok {
return pItem.txn, ok
return pItem.txn, pItem.netFee, ok
}
return nil, false
return nil, 0, false
}
// GetVerifiedTransactions returns a slice of Input from all the transactions in the memory pool
// whose hash is not included in excludedHashes.
func (mp *Pool) GetVerifiedTransactions() []*transaction.Transaction {
func (mp *Pool) GetVerifiedTransactions() []TxWithFee {
mp.lock.RLock()
defer mp.lock.RUnlock()
var t = make([]*transaction.Transaction, len(mp.verifiedTxes))
var i int
var t = make([]TxWithFee, len(mp.verifiedTxes))
for _, p := range mp.verifiedTxes {
t[i] = p.txn
i++
for i := range mp.verifiedTxes {
t[i].Tx = mp.verifiedTxes[i].txn
t[i].Fee = mp.verifiedTxes[i].netFee
}
return t

View file

@ -22,7 +22,7 @@ func (fs *FeerStub) NetworkFee(*transaction.Transaction) util.Fixed8 {
return fs.netFee
}
func (fs *FeerStub) IsLowPriority(*transaction.Transaction) bool {
func (fs *FeerStub) IsLowPriority(util.Fixed8) bool {
return fs.lowPriority
}
@ -37,16 +37,16 @@ func (fs *FeerStub) SystemFee(*transaction.Transaction) util.Fixed8 {
func testMemPoolAddRemoveWithFeer(t *testing.T, fs Feer) {
mp := NewMemPool(10)
tx := newMinerTX(0)
_, ok := mp.TryGetValue(tx.Hash())
_, _, ok := mp.TryGetValue(tx.Hash())
require.Equal(t, false, ok)
require.NoError(t, mp.Add(tx, fs))
// Re-adding should fail.
require.Error(t, mp.Add(tx, fs))
tx2, ok := mp.TryGetValue(tx.Hash())
tx2, _, ok := mp.TryGetValue(tx.Hash())
require.Equal(t, true, ok)
require.Equal(t, tx, tx2)
mp.Remove(tx.Hash())
_, ok = mp.TryGetValue(tx.Hash())
_, _, ok = mp.TryGetValue(tx.Hash())
require.Equal(t, false, ok)
// Make sure nothing left in the mempool after removal.
assert.Equal(t, 0, len(mp.verifiedMap))
@ -173,8 +173,8 @@ func TestGetVerified(t *testing.T) {
require.Equal(t, mempoolSize, mp.Count())
verTxes := mp.GetVerifiedTransactions()
require.Equal(t, mempoolSize, len(verTxes))
for _, tx := range verTxes {
require.Contains(t, txes, tx)
for _, txf := range verTxes {
require.Contains(t, txes, txf.Tx)
}
for _, tx := range txes {
mp.Remove(tx.Hash())
@ -210,8 +210,8 @@ func TestRemoveStale(t *testing.T) {
})
require.Equal(t, mempoolSize/2, mp.Count())
verTxes := mp.GetVerifiedTransactions()
for _, tx := range verTxes {
require.NotContains(t, txes1, tx)
require.Contains(t, txes2, tx)
for _, txf := range verTxes {
require.NotContains(t, txes1, txf.Tx)
require.Contains(t, txes2, txf.Tx)
}
}

View file

@ -13,6 +13,17 @@ import (
"github.com/CityOfZion/neo-go/pkg/vm/opcode"
)
var (
// governingTokenTX represents transaction that is used to create
// governing (NEO) token. It's a part of the genesis block.
governingTokenTX transaction.Transaction
// utilityTokenTX represents transaction that is used to create
// utility (GAS) token. It's a part of the genesis block. It's mostly
// useful for its hash that represents GAS asset ID.
utilityTokenTX transaction.Transaction
)
// createGenesisBlock creates a genesis block based on the given configuration.
func createGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error) {
validators, err := getValidators(cfg)
@ -38,8 +49,6 @@ func createGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error)
},
}
governingTX := governingTokenTX()
utilityTX := utilityTokenTX()
rawScript, err := smartcontract.CreateMultiSigRedeemScript(
len(cfg.StandbyValidators)/2+1,
validators,
@ -62,16 +71,16 @@ func createGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error)
Outputs: []transaction.Output{},
Scripts: []transaction.Witness{},
},
governingTX,
utilityTX,
&governingTokenTX,
&utilityTokenTX,
{
Type: transaction.IssueType,
Data: &transaction.IssueTX{}, // no fields.
Inputs: []transaction.Input{},
Outputs: []transaction.Output{
{
AssetID: governingTX.Hash(),
Amount: governingTX.Data.(*transaction.RegisterTX).Amount,
AssetID: governingTokenTX.Hash(),
Amount: governingTokenTX.Data.(*transaction.RegisterTX).Amount,
ScriptHash: scriptOut,
},
},
@ -92,7 +101,7 @@ func createGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error)
return b, nil
}
func governingTokenTX() *transaction.Transaction {
func init() {
admin := hash.Hash160([]byte{byte(opcode.PUSHT)})
registerTX := &transaction.RegisterTX{
AssetType: transaction.GoverningToken,
@ -102,7 +111,7 @@ func governingTokenTX() *transaction.Transaction {
Admin: admin,
}
tx := &transaction.Transaction{
governingTokenTX = transaction.Transaction{
Type: transaction.RegisterType,
Data: registerTX,
Attributes: []transaction.Attribute{},
@ -111,19 +120,15 @@ func governingTokenTX() *transaction.Transaction {
Scripts: []transaction.Witness{},
}
return tx
}
func utilityTokenTX() *transaction.Transaction {
admin := hash.Hash160([]byte{byte(opcode.PUSHF)})
registerTX := &transaction.RegisterTX{
admin = hash.Hash160([]byte{byte(opcode.PUSHF)})
registerTX = &transaction.RegisterTX{
AssetType: transaction.UtilityToken,
Name: "[{\"lang\":\"zh-CN\",\"name\":\"小蚁币\"},{\"lang\":\"en\",\"name\":\"AntCoin\"}]",
Amount: calculateUtilityAmount(),
Precision: 8,
Admin: admin,
}
tx := &transaction.Transaction{
utilityTokenTX = transaction.Transaction{
Type: transaction.RegisterType,
Data: registerTX,
Attributes: []transaction.Attribute{},
@ -131,8 +136,16 @@ func utilityTokenTX() *transaction.Transaction {
Outputs: []transaction.Output{},
Scripts: []transaction.Witness{},
}
}
return tx
// GoverningTokenID returns the governing token (NEO) hash.
func GoverningTokenID() util.Uint256 {
return governingTokenTX.Hash()
}
// UtilityTokenID returns the utility token (GAS) hash.
func UtilityTokenID() util.Uint256 {
return utilityTokenTX.Hash()
}
func getValidators(cfg config.ProtocolConfiguration) ([]*keys.PublicKey, error) {

View file

@ -50,12 +50,10 @@ func TestGetConsensusAddressMainNet(t *testing.T) {
func TestUtilityTokenTX(t *testing.T) {
expect := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"
tx := utilityTokenTX()
assert.Equal(t, expect, tx.Hash().StringLE())
assert.Equal(t, expect, UtilityTokenID().StringLE())
}
func TestGoverningTokenTX(t *testing.T) {
expect := "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b"
tx := governingTokenTX()
assert.Equal(t, expect, tx.Hash().StringLE())
assert.Equal(t, expect, GoverningTokenID().StringLE())
}

View file

@ -10,6 +10,7 @@ import (
"fmt"
"math/big"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/nspcc-dev/rfc6979"
)
@ -98,10 +99,11 @@ func (p *PrivateKey) Address() string {
return pk.Address()
}
// Signature creates the signature using the private key.
func (p *PrivateKey) Signature() []byte {
// GetScriptHash returns verification script hash for public key associated with
// the private key.
func (p *PrivateKey) GetScriptHash() util.Uint160 {
pk := p.PublicKey()
return pk.Signature()
return pk.GetScriptHash()
}
// Sign signs arbitrary length data using the private key.

View file

@ -12,6 +12,7 @@ import (
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
"github.com/CityOfZion/neo-go/pkg/encoding/address"
"github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/CityOfZion/neo-go/pkg/vm/opcode"
"github.com/pkg/errors"
)
@ -233,18 +234,14 @@ func (p *PublicKey) GetVerificationScript() []byte {
return b
}
// Signature returns a NEO-specific hash of the key.
func (p *PublicKey) Signature() []byte {
sig := hash.Hash160(p.GetVerificationScript())
return sig.BytesBE()
// GetScriptHash returns a Hash160 of verification script for the key.
func (p *PublicKey) GetScriptHash() util.Uint160 {
return hash.Hash160(p.GetVerificationScript())
}
// Address returns a base58-encoded NEO-specific address based on the key hash.
func (p *PublicKey) Address() string {
sig := hash.Hash160(p.GetVerificationScript())
return address.Uint160ToString(sig)
return address.Uint160ToString(p.GetScriptHash())
}
// Verify returns true if the signature is valid and corresponds

View file

@ -26,6 +26,9 @@ type testChain struct {
blockheight uint32
}
func (chain testChain) ApplyPolicyToTxSet([]mempool.TxWithFee) []mempool.TxWithFee {
panic("TODO")
}
func (chain testChain) GetConfig() config.ProtocolConfiguration {
panic("TODO")
}
@ -122,7 +125,7 @@ func (chain testChain) GetMemPool() *mempool.Pool {
panic("TODO")
}
func (chain testChain) IsLowPriority(*transaction.Transaction) bool {
func (chain testChain) IsLowPriority(util.Fixed8) bool {
panic("TODO")
}

View file

@ -64,7 +64,7 @@ type (
unregister chan peerDrop
quit chan struct{}
connected *atomic.Bool
consensusStarted *atomic.Bool
log *zap.Logger
}
@ -88,17 +88,24 @@ func NewServer(config ServerConfig, chain core.Blockchainer, log *zap.Logger) (*
}
s := &Server{
ServerConfig: config,
chain: chain,
id: randomID(),
quit: make(chan struct{}),
register: make(chan Peer),
unregister: make(chan peerDrop),
peers: make(map[Peer]bool),
connected: atomic.NewBool(false),
log: log,
ServerConfig: config,
chain: chain,
id: randomID(),
quit: make(chan struct{}),
register: make(chan Peer),
unregister: make(chan peerDrop),
peers: make(map[Peer]bool),
consensusStarted: atomic.NewBool(false),
log: log,
}
s.bQueue = newBlockQueue(maxBlockBatch, chain, log, s.relayBlock)
s.bQueue = newBlockQueue(maxBlockBatch, chain, log, func(b *block.Block) {
if s.consensusStarted.Load() {
s.consensus.OnNewBlock()
} else {
s.tryStartConsensus()
}
s.relayBlock(b)
})
srv, err := consensus.NewService(consensus.Config{
Logger: log,
@ -274,13 +281,13 @@ func (s *Server) runProto() {
}
func (s *Server) tryStartConsensus() {
if s.Wallet == nil || s.connected.Load() {
if s.Wallet == nil || s.consensusStarted.Load() {
return
}
if s.HandshakedPeersCount() >= s.MinPeers {
s.log.Info("minimum amount of peers were connected to")
if s.connected.CAS(false, true) {
if s.IsInSync() {
s.log.Info("node reached synchronized state, starting consensus")
if s.consensusStarted.CAS(false, true) {
s.consensus.Start()
}
}
@ -336,6 +343,39 @@ func (s *Server) getVersionMsg() *Message {
return s.MkMsg(CMDVersion, payload)
}
// IsInSync answers the question of whether the server is in sync with the
// network or not (at least how the server itself sees it). The server operates
// with the data that it has, the number of peers (that has to be more than
// minimum number) and height of these peers (our chain has to be not lower
// than 2/3 of our peers have). Ideally we would check for the highest of the
// peers, but the problem is that they can lie to us and send whatever height
// they want to.
func (s *Server) IsInSync() bool {
var peersNumber int
var notHigher int
if s.MinPeers == 0 {
return true
}
ourLastBlock := s.chain.BlockHeight()
s.lock.RLock()
for p := range s.peers {
if p.Handshaked() {
peersNumber++
if ourLastBlock >= p.LastBlockIndex() {
notHigher++
}
}
}
s.lock.RUnlock()
// Checking bQueue would also be nice, but it can be filled with garbage
// easily at the moment.
return peersNumber >= s.MinPeers && (3*notHigher > 2*peersNumber) // && s.bQueue.length() == 0
}
// When a peer sends out his version we reply with verack after validating
// the version.
func (s *Server) handleVersionCmd(p Peer, version *payload.Version) error {
@ -746,15 +786,14 @@ func (s *Server) verifyAndPoolTX(t *transaction.Transaction) RelayReason {
if t.Type == transaction.MinerType {
return RelayInvalid
}
// TODO: Implement Plugin.CheckPolicy?
//if (!Plugin.CheckPolicy(transaction))
// return RelayResultReason.PolicyFail;
if err := s.chain.PoolTx(t); err != nil {
switch err {
case core.ErrAlreadyExists:
return RelayAlreadyExists
case core.ErrOOM:
return RelayOutOfMemory
case core.ErrPolicy:
return RelayPolicyFail
default:
return RelayInvalid
}

View file

@ -4,6 +4,7 @@ import (
"encoding/hex"
"fmt"
"github.com/CityOfZion/neo-go/pkg/core"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/smartcontract"
@ -160,15 +161,12 @@ func (c *Client) SignAndPushInvocationTx(script []byte, wif *keys.WIF, gas util.
var txHash util.Uint256
var err error
gasIDB, _ := hex.DecodeString("602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7")
gasID, _ := util.Uint256DecodeBytesLE(gasIDB)
tx := transaction.NewInvocationTX(script, gas)
fromAddress := wif.PrivateKey.Address()
if gas > 0 {
if err = AddInputsAndUnspentsToTx(tx, fromAddress, gasID, gas, c); err != nil {
if err = AddInputsAndUnspentsToTx(tx, fromAddress, core.UtilityTokenID(), gas, c); err != nil {
return txHash, errors.Wrap(err, "failed to add inputs and unspents to transaction")
}
}

1
pkg/wallet/testdata/wallet1.json vendored Normal file
View file

@ -0,0 +1 @@
{"name":"wallet1","version":"1.0","scrypt":{"n":16384,"r":8,"p":8},"accounts":[{"address":"AKkkumHbBipZ46UMZJoFynJMXzSRnBvKcs","label":null,"isDefault":false,"lock":false,"key":"6PYLmjBYJ4wQTCEfqvnznGJwZeW9pfUcV5m5oreHxqryUgqKpTRAFt9L8Y","contract":{"script":"2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2ac","parameters":[{"name":"parameter0","type":"Signature"}],"deployed":false},"extra":null},{"address":"AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU","label":null,"isDefault":false,"lock":false,"key":"6PYLmjBYJ4wQTCEfqvnznGJwZeW9pfUcV5m5oreHxqryUgqKpTRAFt9L8Y","contract":{"script":"532102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd622102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc22103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee69954ae","parameters":[{"name":"parameter0","type":"Signature"},{"name":"parameter1","type":"Signature"},{"name":"parameter2","type":"Signature"}],"deployed":false},"extra":null}],"extra":null}

1
pkg/wallet/testdata/wallet2.json vendored Normal file
View file

@ -0,0 +1 @@
{"name":"wallet2","version":"1.0","scrypt":{"n":16384,"r":8,"p":8},"accounts":[{"address":"AKkkumHbBipZ46UMZJoFynJMXzSRnBvKcs","label":null,"isDefault":false,"lock":false,"key":"6PYLmjBYJ4wQTCEfqvnznGJwZeW9pfUcV5m5oreHxqryUgqKpTRAFt9L8Y","contract":{"script":"2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2ac","parameters":[{"name":"parameter0","type":"Signature"}],"deployed":false},"extra":null},{"address":"AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU","label":null,"isDefault":false,"lock":false,"key":"6PYXHjPaNvW8YknSXaKsTWjf9FRxo1s4naV2jdmSQEgzaqKGX368rndN3L","contract":{"script":"532102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd622102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc22103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee69954ae","parameters":[{"name":"parameter0","type":"Signature"},{"name":"parameter1","type":"Signature"},{"name":"parameter2","type":"Signature"}],"deployed":false},"extra":null},{"address":"AWLYWXB8C9Lt1nHdDZJnC5cpYJjgRDLk17","label":null,"isDefault":true,"lock":false,"key":"6PYXHjPaNvW8YknSXaKsTWjf9FRxo1s4naV2jdmSQEgzaqKGX368rndN3L","contract":{"script":"2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406eac","parameters":[{"name":"parameter0","type":"Signature"}],"deployed":false},"extra":null}],"extra":null}

View file

@ -7,6 +7,7 @@ import (
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/CityOfZion/neo-go/pkg/vm"
)
const (
@ -129,3 +130,24 @@ func (w *Wallet) GetAccount(h util.Uint160) *Account {
return nil
}
// GetChangeAddress returns the default address to send transaction's change to.
func (w *Wallet) GetChangeAddress() util.Uint160 {
var res util.Uint160
var acc *Account
for i := range w.Accounts {
if acc == nil || w.Accounts[i].Default {
if w.Accounts[i].Contract != nil && vm.IsSignatureContract(w.Accounts[i].Contract.Script) {
acc = w.Accounts[i]
if w.Accounts[i].Default {
break
}
}
}
}
if acc != nil {
res = acc.Contract.ScriptHash()
}
return res
}

View file

@ -6,6 +6,7 @@ import (
"os"
"testing"
"github.com/CityOfZion/neo-go/pkg/encoding/address"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -145,3 +146,20 @@ func TestWallet_GetAccount(t *testing.T) {
assert.Equal(t, acc, wallet.GetAccount(h), "can't get %d account", i)
}
}
func TestWalletGetChangeAddress(t *testing.T) {
w1, err := NewWalletFromFile("testdata/wallet1.json")
require.NoError(t, err)
sh := w1.GetChangeAddress()
// No default address, the first one is used.
expected, err := address.StringToUint160("AKkkumHbBipZ46UMZJoFynJMXzSRnBvKcs")
require.NoError(t, err)
require.Equal(t, expected, sh)
w2, err := NewWalletFromFile("testdata/wallet2.json")
require.NoError(t, err)
sh = w2.GetChangeAddress()
// Default address.
expected, err = address.StringToUint160("AWLYWXB8C9Lt1nHdDZJnC5cpYJjgRDLk17")
require.NoError(t, err)
require.Equal(t, expected, sh)
}