commit
6dc9023289
24 changed files with 323 additions and 125 deletions
|
@ -46,7 +46,7 @@ type (
|
||||||
AddressVersion byte `yaml:"AddressVersion"`
|
AddressVersion byte `yaml:"AddressVersion"`
|
||||||
SecondsPerBlock int `yaml:"SecondsPerBlock"`
|
SecondsPerBlock int `yaml:"SecondsPerBlock"`
|
||||||
LowPriorityThreshold float64 `yaml:"LowPriorityThreshold"`
|
LowPriorityThreshold float64 `yaml:"LowPriorityThreshold"`
|
||||||
MaxTransactionsPerBlock int64 `yaml:"MaxTransactionsPerBlock"`
|
MaxTransactionsPerBlock int `yaml:"MaxTransactionsPerBlock"`
|
||||||
MemPoolSize int `yaml:"MemPoolSize"`
|
MemPoolSize int `yaml:"MemPoolSize"`
|
||||||
StandbyValidators []string `yaml:"StandbyValidators"`
|
StandbyValidators []string `yaml:"StandbyValidators"`
|
||||||
SeedList []string `yaml:"SeedList"`
|
SeedList []string `yaml:"SeedList"`
|
||||||
|
@ -59,6 +59,13 @@ type (
|
||||||
FreeGasLimit util.Fixed8 `yaml:"FreeGasLimit"`
|
FreeGasLimit util.Fixed8 `yaml:"FreeGasLimit"`
|
||||||
// SaveStorageBatch enables storage batch saving before every persist.
|
// SaveStorageBatch enables storage batch saving before every persist.
|
||||||
SaveStorageBatch bool `yaml:"SaveStorageBatch"`
|
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.
|
// SystemFee fees related to system.
|
||||||
|
|
|
@ -31,6 +31,10 @@ ProtocolConfiguration:
|
||||||
VerifyBlocks: true
|
VerifyBlocks: true
|
||||||
VerifyTransactions: false
|
VerifyTransactions: false
|
||||||
FreeGasLimit: 10.0
|
FreeGasLimit: 10.0
|
||||||
|
MaxTransactionsPerBlock: 500
|
||||||
|
MaxFreeTransactionsPerBlock: 20
|
||||||
|
MaxFreeTransactionSize: 1024
|
||||||
|
FeePerExtraByte: 0.00001
|
||||||
|
|
||||||
ApplicationConfiguration:
|
ApplicationConfiguration:
|
||||||
# LogPath could be set up in case you need stdout logs to some proper file.
|
# LogPath could be set up in case you need stdout logs to some proper file.
|
||||||
|
|
|
@ -31,6 +31,10 @@ ProtocolConfiguration:
|
||||||
VerifyBlocks: true
|
VerifyBlocks: true
|
||||||
VerifyTransactions: false
|
VerifyTransactions: false
|
||||||
FreeGasLimit: 10.0
|
FreeGasLimit: 10.0
|
||||||
|
MaxTransactionsPerBlock: 500
|
||||||
|
MaxFreeTransactionsPerBlock: 20
|
||||||
|
MaxFreeTransactionSize: 1024
|
||||||
|
FeePerExtraByte: 0.00001
|
||||||
|
|
||||||
ApplicationConfiguration:
|
ApplicationConfiguration:
|
||||||
# LogPath could be set up in case you need stdout logs to some proper file.
|
# LogPath could be set up in case you need stdout logs to some proper file.
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -7,7 +7,7 @@ require (
|
||||||
github.com/go-redis/redis v6.10.2+incompatible
|
github.com/go-redis/redis v6.10.2+incompatible
|
||||||
github.com/go-yaml/yaml v2.1.0+incompatible
|
github.com/go-yaml/yaml v2.1.0+incompatible
|
||||||
github.com/mr-tron/base58 v1.1.2
|
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/nspcc-dev/rfc6979 v0.2.0
|
||||||
github.com/pkg/errors v0.8.1
|
github.com/pkg/errors v0.8.1
|
||||||
github.com/prometheus/client_golang v1.2.1
|
github.com/prometheus/client_golang v1.2.1
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -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-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 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-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-20200218131838-be55fd41ea78 h1:cb8hWea3yyqbxzmnqqSSgqVCRoKNrFaS99Cqt0Qm7nQ=
|
||||||
github.com/nspcc-dev/dbft v0.0.0-20200211143830-4deeb124d7f9/go.mod h1:O0qtn62prQSqizzoagHmuuKoz8QMkU3SzBoKdEvm3aQ=
|
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 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.0/go.mod h1:F/96fUzPM3wR+UGsPi3faVNmFlA9KAEAUQR7dMxZmNA=
|
||||||
github.com/nspcc-dev/neofs-crypto v0.2.3 h1:aca3X2aly92ENRbFK+kH6Hd+J9EQ4Eu6XMVoITSIKtc=
|
github.com/nspcc-dev/neofs-crypto v0.2.3 h1:aca3X2aly92ENRbFK+kH6Hd+J9EQ4Eu6XMVoITSIKtc=
|
||||||
|
|
|
@ -9,8 +9,8 @@ import (
|
||||||
"github.com/CityOfZion/neo-go/config"
|
"github.com/CityOfZion/neo-go/config"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core"
|
"github.com/CityOfZion/neo-go/pkg/core"
|
||||||
coreb "github.com/CityOfZion/neo-go/pkg/core/block"
|
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/core/transaction"
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||||
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
@ -42,6 +42,9 @@ type Service interface {
|
||||||
OnTransaction(tx *transaction.Transaction)
|
OnTransaction(tx *transaction.Transaction)
|
||||||
// GetPayload returns Payload with specified hash if it is present in the local cache.
|
// GetPayload returns Payload with specified hash if it is present in the local cache.
|
||||||
GetPayload(h util.Uint256) *Payload
|
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 {
|
type service struct {
|
||||||
|
@ -57,6 +60,9 @@ type service struct {
|
||||||
// everything in single thread.
|
// everything in single thread.
|
||||||
messages chan Payload
|
messages chan Payload
|
||||||
transactions chan *transaction.Transaction
|
transactions chan *transaction.Transaction
|
||||||
|
// blockEvents is used to pass a new block event to the consensus
|
||||||
|
// process.
|
||||||
|
blockEvents chan struct{}
|
||||||
lastProposal []util.Uint256
|
lastProposal []util.Uint256
|
||||||
wallet *wallet.Wallet
|
wallet *wallet.Wallet
|
||||||
}
|
}
|
||||||
|
@ -101,6 +107,7 @@ func NewService(cfg Config) (Service, error) {
|
||||||
messages: make(chan Payload, 100),
|
messages: make(chan Payload, 100),
|
||||||
|
|
||||||
transactions: make(chan *transaction.Transaction, 100),
|
transactions: make(chan *transaction.Transaction, 100),
|
||||||
|
blockEvents: make(chan struct{}, 1),
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Wallet == nil {
|
if cfg.Wallet == nil {
|
||||||
|
@ -168,14 +175,7 @@ func (s *service) eventLoop() {
|
||||||
s.log.Debug("timer fired",
|
s.log.Debug("timer fired",
|
||||||
zap.Uint32("height", hv.Height),
|
zap.Uint32("height", hv.Height),
|
||||||
zap.Uint("view", uint(hv.View)))
|
zap.Uint("view", uint(hv.View)))
|
||||||
if s.Chain.BlockHeight() >= s.dbft.BlockIndex {
|
s.dbft.OnTimeout(hv)
|
||||||
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)
|
|
||||||
}
|
|
||||||
case msg := <-s.messages:
|
case msg := <-s.messages:
|
||||||
fields := []zap.Field{
|
fields := []zap.Field{
|
||||||
zap.Uint16("from", msg.validatorIndex),
|
zap.Uint16("from", msg.validatorIndex),
|
||||||
|
@ -204,6 +204,11 @@ func (s *service) eventLoop() {
|
||||||
s.dbft.OnReceive(&msg)
|
s.dbft.OnReceive(&msg)
|
||||||
case tx := <-s.transactions:
|
case tx := <-s.transactions:
|
||||||
s.dbft.OnTransaction(tx)
|
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]
|
pub := validators[p.validatorIndex]
|
||||||
vs := pub.(*publicKey).GetVerificationScript()
|
h := pub.(*publicKey).GetScriptHash()
|
||||||
h := hash.Hash160(vs)
|
|
||||||
|
|
||||||
return p.Verify(h)
|
return p.Verify(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) getKeyPair(pubs []crypto.PublicKey) (int, crypto.PrivateKey, crypto.PublicKey) {
|
func (s *service) getKeyPair(pubs []crypto.PublicKey) (int, crypto.PrivateKey, crypto.PublicKey) {
|
||||||
for i := range pubs {
|
for i := range pubs {
|
||||||
script := pubs[i].(*publicKey).GetVerificationScript()
|
sh := pubs[i].(*publicKey).GetScriptHash()
|
||||||
acc := s.wallet.GetAccount(hash.Hash160(script))
|
acc := s.wallet.GetAccount(sh)
|
||||||
if acc == nil {
|
if acc == nil {
|
||||||
continue
|
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.
|
// GetPayload returns payload stored in cache.
|
||||||
func (s *service) GetPayload(h util.Uint256) *Payload {
|
func (s *service) GetPayload(h util.Uint256) *Payload {
|
||||||
p := s.cache.Get(h)
|
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 {
|
func (s *service) getVerifiedTx(count int) []block.Transaction {
|
||||||
pool := s.Config.Chain.GetMemPool()
|
pool := s.Config.Chain.GetMemPool()
|
||||||
|
|
||||||
var txx []*transaction.Transaction
|
var txx []mempool.TxWithFee
|
||||||
|
|
||||||
if s.dbft.ViewNumber > 0 {
|
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 {
|
for i := range s.lastProposal {
|
||||||
if tx, ok := pool.TryGetValue(s.lastProposal[i]); ok {
|
if tx, fee, ok := pool.TryGetValue(s.lastProposal[i]); ok {
|
||||||
txx = append(txx, tx)
|
txx = append(txx, mempool.TxWithFee{Tx: tx, Fee: fee})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -410,11 +428,30 @@ func (s *service) getVerifiedTx(count int) []block.Transaction {
|
||||||
txx = pool.GetVerifiedTransactions()
|
txx = pool.GetVerifiedTransactions()
|
||||||
}
|
}
|
||||||
|
|
||||||
res := make([]block.Transaction, len(txx)+1)
|
if len(txx) > 0 {
|
||||||
for i := range txx {
|
txx = s.Config.Chain.ApplyPolicyToTxSet(txx)
|
||||||
res[i+1] = txx[i]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
for {
|
||||||
nonce := rand.Uint32()
|
nonce := rand.Uint32()
|
||||||
res[0] = &transaction.Transaction{
|
res[0] = &transaction.Transaction{
|
||||||
|
@ -423,7 +460,7 @@ func (s *service) getVerifiedTx(count int) []block.Transaction {
|
||||||
Data: &transaction.MinerTX{Nonce: nonce},
|
Data: &transaction.MinerTX{Nonce: nonce},
|
||||||
Attributes: nil,
|
Attributes: nil,
|
||||||
Inputs: nil,
|
Inputs: nil,
|
||||||
Outputs: nil,
|
Outputs: txOuts,
|
||||||
Scripts: nil,
|
Scripts: nil,
|
||||||
Trimmed: false,
|
Trimmed: false,
|
||||||
}
|
}
|
||||||
|
|
|
@ -239,6 +239,6 @@ func newTestChain(t *testing.T) *core.Blockchain {
|
||||||
type feer struct{}
|
type feer struct{}
|
||||||
|
|
||||||
func (fs *feer) NetworkFee(*transaction.Transaction) util.Fixed8 { return util.Fixed8(0) }
|
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) FeePerByte(*transaction.Transaction) util.Fixed8 { return util.Fixed8(0) }
|
||||||
func (fs *feer) SystemFee(*transaction.Transaction) util.Fixed8 { return util.Fixed8(0) }
|
func (fs *feer) SystemFee(*transaction.Transaction) util.Fixed8 { return util.Fixed8(0) }
|
||||||
|
|
|
@ -49,6 +49,9 @@ var (
|
||||||
// ErrOOM is returned when adding transaction to the memory pool because
|
// ErrOOM is returned when adding transaction to the memory pool because
|
||||||
// it reached its full capacity.
|
// it reached its full capacity.
|
||||||
ErrOOM = errors.New("no space left in the memory pool")
|
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 (
|
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}
|
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
|
cfg.MemPoolSize = defaultMemPoolSize
|
||||||
log.Info("mempool size is not set or wrong, setting default value", zap.Int("MemPoolSize", cfg.MemPoolSize))
|
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{
|
bc := &Blockchain{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
dao: newDao(s),
|
dao: newDao(s),
|
||||||
|
@ -432,7 +451,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if prevTXOutput.AssetID.Equals(governingTokenTX().Hash()) {
|
if prevTXOutput.AssetID.Equals(GoverningTokenID()) {
|
||||||
spentCoin := NewSpentCoinState(input.PrevHash, prevTXHeight)
|
spentCoin := NewSpentCoinState(input.PrevHash, prevTXHeight)
|
||||||
spentCoin.items[input.PrevIndex] = block.Index
|
spentCoin.items[input.PrevIndex] = block.Index
|
||||||
if err = cache.PutSpentCoinState(input.PrevHash, spentCoin); err != nil {
|
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 {
|
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 modAccountVotes(account, dao, output.Amount)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func processTXWithValidatorsSubtract(output *transaction.Output, account *state.Account, dao *cachedDao) error {
|
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 modAccountVotes(account, dao, -output.Amount)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -724,7 +743,7 @@ func processAccountStateDescriptor(descriptor *transaction.StateDescriptor, dao
|
||||||
}
|
}
|
||||||
|
|
||||||
if descriptor.Field == "Votes" {
|
if descriptor.Field == "Votes" {
|
||||||
balance := account.GetBalanceValues()[governingTokenTX().Hash()]
|
balance := account.GetBalanceValues()[GoverningTokenID()]
|
||||||
if err = modAccountVotes(account, dao, -balance); err != nil {
|
if err = modAccountVotes(account, dao, -balance); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -814,7 +833,7 @@ func (bc *Blockchain) headerListLen() (n int) {
|
||||||
|
|
||||||
// GetTransaction returns a TX and its height by the given hash.
|
// GetTransaction returns a TX and its height by the given hash.
|
||||||
func (bc *Blockchain) GetTransaction(hash util.Uint256) (*transaction.Transaction, uint32, error) {
|
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 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)
|
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 {
|
func (bc *Blockchain) NetworkFee(t *transaction.Transaction) util.Fixed8 {
|
||||||
inputAmount := util.Fixed8FromInt64(0)
|
inputAmount := util.Fixed8FromInt64(0)
|
||||||
for _, txOutput := range bc.References(t) {
|
for _, txOutput := range bc.References(t) {
|
||||||
if txOutput.AssetID == utilityTokenTX().Hash() {
|
if txOutput.AssetID == UtilityTokenID() {
|
||||||
inputAmount.Add(txOutput.Amount)
|
inputAmount.Add(txOutput.Amount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
outputAmount := util.Fixed8FromInt64(0)
|
outputAmount := util.Fixed8FromInt64(0)
|
||||||
for _, txOutput := range t.Outputs {
|
for _, txOutput := range t.Outputs {
|
||||||
if txOutput.AssetID == utilityTokenTX().Hash() {
|
if txOutput.AssetID == UtilityTokenID() {
|
||||||
outputAmount.Add(txOutput.Amount)
|
outputAmount.Add(txOutput.Amount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1019,10 +1038,10 @@ func (bc *Blockchain) SystemFee(t *transaction.Transaction) util.Fixed8 {
|
||||||
return bc.GetConfig().SystemFee.TryGetValue(t.Type)
|
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.
|
// LowPriorityThreshold.
|
||||||
func (bc *Blockchain) IsLowPriority(t *transaction.Transaction) bool {
|
func (bc *Blockchain) IsLowPriority(fee util.Fixed8) bool {
|
||||||
return bc.NetworkFee(t) < util.Fixed8FromFloat(bc.GetConfig().LowPriorityThreshold)
|
return fee < util.Fixed8FromFloat(bc.GetConfig().LowPriorityThreshold)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMemPool returns the memory pool of the blockchain.
|
// GetMemPool returns the memory pool of the blockchain.
|
||||||
|
@ -1030,6 +1049,24 @@ func (bc *Blockchain) GetMemPool() *mempool.Pool {
|
||||||
return &bc.memPool
|
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.
|
// VerifyBlock verifies block against its current state.
|
||||||
func (bc *Blockchain) VerifyBlock(block *block.Block) error {
|
func (bc *Blockchain) VerifyBlock(block *block.Block) error {
|
||||||
prevHeader, err := bc.GetHeader(block.PrevHash)
|
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 {
|
if err := bc.verifyTx(t, nil); err != nil {
|
||||||
return err
|
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 {
|
if err := bc.memPool.Add(t, bc); err != nil {
|
||||||
switch err {
|
switch err {
|
||||||
case mempool.ErrOOM:
|
case mempool.ErrOOM:
|
||||||
|
@ -1192,7 +1241,7 @@ func (bc *Blockchain) verifyResults(t *transaction.Transaction) error {
|
||||||
if len(resultsDestroy) > 1 {
|
if len(resultsDestroy) > 1 {
|
||||||
return errors.New("tx has more than 1 destroy output")
|
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")
|
return errors.New("tx destroys non-utility token")
|
||||||
}
|
}
|
||||||
sysfee := bc.SystemFee(t)
|
sysfee := bc.SystemFee(t)
|
||||||
|
@ -1208,14 +1257,14 @@ func (bc *Blockchain) verifyResults(t *transaction.Transaction) error {
|
||||||
switch t.Type {
|
switch t.Type {
|
||||||
case transaction.MinerType, transaction.ClaimType:
|
case transaction.MinerType, transaction.ClaimType:
|
||||||
for _, r := range resultsIssue {
|
for _, r := range resultsIssue {
|
||||||
if r.AssetID != utilityTokenTX().Hash() {
|
if r.AssetID != UtilityTokenID() {
|
||||||
return errors.New("miner or claim tx issues non-utility tokens")
|
return errors.New("miner or claim tx issues non-utility tokens")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case transaction.IssueType:
|
case transaction.IssueType:
|
||||||
for _, r := range resultsIssue {
|
for _, r := range resultsIssue {
|
||||||
if r.AssetID == utilityTokenTX().Hash() {
|
if r.AssetID == UtilityTokenID() {
|
||||||
return errors.New("issue tx issues utility tokens")
|
return errors.New("issue tx issues utility tokens")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1409,20 +1458,20 @@ func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.P
|
||||||
}
|
}
|
||||||
|
|
||||||
uniqueSBValidators := standByValidators.Unique()
|
uniqueSBValidators := standByValidators.Unique()
|
||||||
pubKeys := keys.PublicKeys{}
|
result := keys.PublicKeys{}
|
||||||
for _, validator := range validators {
|
for _, validator := range validators {
|
||||||
if validator.RegisteredAndHasVotes() || uniqueSBValidators.Contains(validator.PublicKey) {
|
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()
|
if result.Len() >= count {
|
||||||
for i := 0; i < uniqueSBValidators.Len() && result.Len() < count; i++ {
|
result = result[:count]
|
||||||
if !result.Contains(uniqueSBValidators[i]) {
|
} else {
|
||||||
result = append(result, uniqueSBValidators[i])
|
for i := 0; i < uniqueSBValidators.Len() && result.Len() < count; i++ {
|
||||||
|
if !result.Contains(uniqueSBValidators[i]) {
|
||||||
|
result = append(result, uniqueSBValidators[i])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sort.Sort(result)
|
sort.Sort(result)
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
// Blockchainer is an interface that abstract the implementation
|
// Blockchainer is an interface that abstract the implementation
|
||||||
// of the blockchain.
|
// of the blockchain.
|
||||||
type Blockchainer interface {
|
type Blockchainer interface {
|
||||||
|
ApplyPolicyToTxSet([]mempool.TxWithFee) []mempool.TxWithFee
|
||||||
GetConfig() config.ProtocolConfiguration
|
GetConfig() config.ProtocolConfiguration
|
||||||
AddHeaders(...*block.Header) error
|
AddHeaders(...*block.Header) error
|
||||||
AddBlock(*block.Block) error
|
AddBlock(*block.Block) error
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/block"
|
"github.com/CityOfZion/neo-go/pkg/core/block"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/state"
|
"github.com/CityOfZion/neo-go/pkg/core/state"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||||
"github.com/CityOfZion/neo-go/pkg/smartcontract/trigger"
|
"github.com/CityOfZion/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"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
|
// checkKeyedWitness checks hash of signature check contract with a given public
|
||||||
// key against current list of script hashes for verifying in the interop context.
|
// key against current list of script hashes for verifying in the interop context.
|
||||||
func (ic *interopContext) checkKeyedWitness(key *keys.PublicKey) (bool, error) {
|
func (ic *interopContext) checkKeyedWitness(key *keys.PublicKey) (bool, error) {
|
||||||
return ic.checkHashedWitness(hash.Hash160(key.GetVerificationScript()))
|
return ic.checkHashedWitness(key.GetScriptHash())
|
||||||
}
|
}
|
||||||
|
|
||||||
// runtimeCheckWitness checks witnesses.
|
// runtimeCheckWitness checks witnesses.
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
// Feer is an interface that abstract the implementation of the fee calculation.
|
// Feer is an interface that abstract the implementation of the fee calculation.
|
||||||
type Feer interface {
|
type Feer interface {
|
||||||
NetworkFee(t *transaction.Transaction) util.Fixed8
|
NetworkFee(t *transaction.Transaction) util.Fixed8
|
||||||
IsLowPriority(t *transaction.Transaction) bool
|
IsLowPriority(util.Fixed8) bool
|
||||||
FeePerByte(t *transaction.Transaction) util.Fixed8
|
FeePerByte(t *transaction.Transaction) util.Fixed8
|
||||||
SystemFee(t *transaction.Transaction) util.Fixed8
|
SystemFee(t *transaction.Transaction) util.Fixed8
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,12 @@ type item struct {
|
||||||
// items is a slice of item.
|
// items is a slice of item.
|
||||||
type items []*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.
|
// Pool stores the unconfirms transactions.
|
||||||
type Pool struct {
|
type Pool struct {
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
|
@ -128,8 +134,8 @@ func (mp *Pool) Add(t *transaction.Transaction, fee Feer) error {
|
||||||
timeStamp: time.Now().UTC(),
|
timeStamp: time.Now().UTC(),
|
||||||
perByteFee: fee.FeePerByte(t),
|
perByteFee: fee.FeePerByte(t),
|
||||||
netFee: fee.NetworkFee(t),
|
netFee: fee.NetworkFee(t),
|
||||||
isLowPrio: fee.IsLowPriority(t),
|
|
||||||
}
|
}
|
||||||
|
pItem.isLowPrio = fee.IsLowPriority(pItem.netFee)
|
||||||
mp.lock.Lock()
|
mp.lock.Lock()
|
||||||
if !mp.verifyInputs(t) {
|
if !mp.verifyInputs(t) {
|
||||||
mp.lock.Unlock()
|
mp.lock.Unlock()
|
||||||
|
@ -224,29 +230,28 @@ func NewMemPool(capacity int) Pool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TryGetValue returns a transaction if it exists in the memory pool.
|
// TryGetValue returns a transaction and its fee if it exists in the memory pool.
|
||||||
func (mp *Pool) TryGetValue(hash util.Uint256) (*transaction.Transaction, bool) {
|
func (mp *Pool) TryGetValue(hash util.Uint256) (*transaction.Transaction, util.Fixed8, bool) {
|
||||||
mp.lock.RLock()
|
mp.lock.RLock()
|
||||||
defer mp.lock.RUnlock()
|
defer mp.lock.RUnlock()
|
||||||
if pItem, ok := mp.verifiedMap[hash]; ok {
|
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
|
// GetVerifiedTransactions returns a slice of Input from all the transactions in the memory pool
|
||||||
// whose hash is not included in excludedHashes.
|
// whose hash is not included in excludedHashes.
|
||||||
func (mp *Pool) GetVerifiedTransactions() []*transaction.Transaction {
|
func (mp *Pool) GetVerifiedTransactions() []TxWithFee {
|
||||||
mp.lock.RLock()
|
mp.lock.RLock()
|
||||||
defer mp.lock.RUnlock()
|
defer mp.lock.RUnlock()
|
||||||
|
|
||||||
var t = make([]*transaction.Transaction, len(mp.verifiedTxes))
|
var t = make([]TxWithFee, len(mp.verifiedTxes))
|
||||||
var i int
|
|
||||||
|
|
||||||
for _, p := range mp.verifiedTxes {
|
for i := range mp.verifiedTxes {
|
||||||
t[i] = p.txn
|
t[i].Tx = mp.verifiedTxes[i].txn
|
||||||
i++
|
t[i].Fee = mp.verifiedTxes[i].netFee
|
||||||
}
|
}
|
||||||
|
|
||||||
return t
|
return t
|
||||||
|
|
|
@ -22,7 +22,7 @@ func (fs *FeerStub) NetworkFee(*transaction.Transaction) util.Fixed8 {
|
||||||
return fs.netFee
|
return fs.netFee
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FeerStub) IsLowPriority(*transaction.Transaction) bool {
|
func (fs *FeerStub) IsLowPriority(util.Fixed8) bool {
|
||||||
return fs.lowPriority
|
return fs.lowPriority
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,16 +37,16 @@ func (fs *FeerStub) SystemFee(*transaction.Transaction) util.Fixed8 {
|
||||||
func testMemPoolAddRemoveWithFeer(t *testing.T, fs Feer) {
|
func testMemPoolAddRemoveWithFeer(t *testing.T, fs Feer) {
|
||||||
mp := NewMemPool(10)
|
mp := NewMemPool(10)
|
||||||
tx := newMinerTX(0)
|
tx := newMinerTX(0)
|
||||||
_, ok := mp.TryGetValue(tx.Hash())
|
_, _, ok := mp.TryGetValue(tx.Hash())
|
||||||
require.Equal(t, false, ok)
|
require.Equal(t, false, ok)
|
||||||
require.NoError(t, mp.Add(tx, fs))
|
require.NoError(t, mp.Add(tx, fs))
|
||||||
// Re-adding should fail.
|
// Re-adding should fail.
|
||||||
require.Error(t, mp.Add(tx, fs))
|
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, true, ok)
|
||||||
require.Equal(t, tx, tx2)
|
require.Equal(t, tx, tx2)
|
||||||
mp.Remove(tx.Hash())
|
mp.Remove(tx.Hash())
|
||||||
_, ok = mp.TryGetValue(tx.Hash())
|
_, _, ok = mp.TryGetValue(tx.Hash())
|
||||||
require.Equal(t, false, ok)
|
require.Equal(t, false, ok)
|
||||||
// Make sure nothing left in the mempool after removal.
|
// Make sure nothing left in the mempool after removal.
|
||||||
assert.Equal(t, 0, len(mp.verifiedMap))
|
assert.Equal(t, 0, len(mp.verifiedMap))
|
||||||
|
@ -173,8 +173,8 @@ func TestGetVerified(t *testing.T) {
|
||||||
require.Equal(t, mempoolSize, mp.Count())
|
require.Equal(t, mempoolSize, mp.Count())
|
||||||
verTxes := mp.GetVerifiedTransactions()
|
verTxes := mp.GetVerifiedTransactions()
|
||||||
require.Equal(t, mempoolSize, len(verTxes))
|
require.Equal(t, mempoolSize, len(verTxes))
|
||||||
for _, tx := range verTxes {
|
for _, txf := range verTxes {
|
||||||
require.Contains(t, txes, tx)
|
require.Contains(t, txes, txf.Tx)
|
||||||
}
|
}
|
||||||
for _, tx := range txes {
|
for _, tx := range txes {
|
||||||
mp.Remove(tx.Hash())
|
mp.Remove(tx.Hash())
|
||||||
|
@ -210,8 +210,8 @@ func TestRemoveStale(t *testing.T) {
|
||||||
})
|
})
|
||||||
require.Equal(t, mempoolSize/2, mp.Count())
|
require.Equal(t, mempoolSize/2, mp.Count())
|
||||||
verTxes := mp.GetVerifiedTransactions()
|
verTxes := mp.GetVerifiedTransactions()
|
||||||
for _, tx := range verTxes {
|
for _, txf := range verTxes {
|
||||||
require.NotContains(t, txes1, tx)
|
require.NotContains(t, txes1, txf.Tx)
|
||||||
require.Contains(t, txes2, tx)
|
require.Contains(t, txes2, txf.Tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,17 @@ import (
|
||||||
"github.com/CityOfZion/neo-go/pkg/vm/opcode"
|
"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.
|
// createGenesisBlock creates a genesis block based on the given configuration.
|
||||||
func createGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error) {
|
func createGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error) {
|
||||||
validators, err := getValidators(cfg)
|
validators, err := getValidators(cfg)
|
||||||
|
@ -38,8 +49,6 @@ func createGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
governingTX := governingTokenTX()
|
|
||||||
utilityTX := utilityTokenTX()
|
|
||||||
rawScript, err := smartcontract.CreateMultiSigRedeemScript(
|
rawScript, err := smartcontract.CreateMultiSigRedeemScript(
|
||||||
len(cfg.StandbyValidators)/2+1,
|
len(cfg.StandbyValidators)/2+1,
|
||||||
validators,
|
validators,
|
||||||
|
@ -62,16 +71,16 @@ func createGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error)
|
||||||
Outputs: []transaction.Output{},
|
Outputs: []transaction.Output{},
|
||||||
Scripts: []transaction.Witness{},
|
Scripts: []transaction.Witness{},
|
||||||
},
|
},
|
||||||
governingTX,
|
&governingTokenTX,
|
||||||
utilityTX,
|
&utilityTokenTX,
|
||||||
{
|
{
|
||||||
Type: transaction.IssueType,
|
Type: transaction.IssueType,
|
||||||
Data: &transaction.IssueTX{}, // no fields.
|
Data: &transaction.IssueTX{}, // no fields.
|
||||||
Inputs: []transaction.Input{},
|
Inputs: []transaction.Input{},
|
||||||
Outputs: []transaction.Output{
|
Outputs: []transaction.Output{
|
||||||
{
|
{
|
||||||
AssetID: governingTX.Hash(),
|
AssetID: governingTokenTX.Hash(),
|
||||||
Amount: governingTX.Data.(*transaction.RegisterTX).Amount,
|
Amount: governingTokenTX.Data.(*transaction.RegisterTX).Amount,
|
||||||
ScriptHash: scriptOut,
|
ScriptHash: scriptOut,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -92,7 +101,7 @@ func createGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error)
|
||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func governingTokenTX() *transaction.Transaction {
|
func init() {
|
||||||
admin := hash.Hash160([]byte{byte(opcode.PUSHT)})
|
admin := hash.Hash160([]byte{byte(opcode.PUSHT)})
|
||||||
registerTX := &transaction.RegisterTX{
|
registerTX := &transaction.RegisterTX{
|
||||||
AssetType: transaction.GoverningToken,
|
AssetType: transaction.GoverningToken,
|
||||||
|
@ -102,7 +111,7 @@ func governingTokenTX() *transaction.Transaction {
|
||||||
Admin: admin,
|
Admin: admin,
|
||||||
}
|
}
|
||||||
|
|
||||||
tx := &transaction.Transaction{
|
governingTokenTX = transaction.Transaction{
|
||||||
Type: transaction.RegisterType,
|
Type: transaction.RegisterType,
|
||||||
Data: registerTX,
|
Data: registerTX,
|
||||||
Attributes: []transaction.Attribute{},
|
Attributes: []transaction.Attribute{},
|
||||||
|
@ -111,19 +120,15 @@ func governingTokenTX() *transaction.Transaction {
|
||||||
Scripts: []transaction.Witness{},
|
Scripts: []transaction.Witness{},
|
||||||
}
|
}
|
||||||
|
|
||||||
return tx
|
admin = hash.Hash160([]byte{byte(opcode.PUSHF)})
|
||||||
}
|
registerTX = &transaction.RegisterTX{
|
||||||
|
|
||||||
func utilityTokenTX() *transaction.Transaction {
|
|
||||||
admin := hash.Hash160([]byte{byte(opcode.PUSHF)})
|
|
||||||
registerTX := &transaction.RegisterTX{
|
|
||||||
AssetType: transaction.UtilityToken,
|
AssetType: transaction.UtilityToken,
|
||||||
Name: "[{\"lang\":\"zh-CN\",\"name\":\"小蚁币\"},{\"lang\":\"en\",\"name\":\"AntCoin\"}]",
|
Name: "[{\"lang\":\"zh-CN\",\"name\":\"小蚁币\"},{\"lang\":\"en\",\"name\":\"AntCoin\"}]",
|
||||||
Amount: calculateUtilityAmount(),
|
Amount: calculateUtilityAmount(),
|
||||||
Precision: 8,
|
Precision: 8,
|
||||||
Admin: admin,
|
Admin: admin,
|
||||||
}
|
}
|
||||||
tx := &transaction.Transaction{
|
utilityTokenTX = transaction.Transaction{
|
||||||
Type: transaction.RegisterType,
|
Type: transaction.RegisterType,
|
||||||
Data: registerTX,
|
Data: registerTX,
|
||||||
Attributes: []transaction.Attribute{},
|
Attributes: []transaction.Attribute{},
|
||||||
|
@ -131,8 +136,16 @@ func utilityTokenTX() *transaction.Transaction {
|
||||||
Outputs: []transaction.Output{},
|
Outputs: []transaction.Output{},
|
||||||
Scripts: []transaction.Witness{},
|
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) {
|
func getValidators(cfg config.ProtocolConfiguration) ([]*keys.PublicKey, error) {
|
||||||
|
|
|
@ -50,12 +50,10 @@ func TestGetConsensusAddressMainNet(t *testing.T) {
|
||||||
|
|
||||||
func TestUtilityTokenTX(t *testing.T) {
|
func TestUtilityTokenTX(t *testing.T) {
|
||||||
expect := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"
|
expect := "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"
|
||||||
tx := utilityTokenTX()
|
assert.Equal(t, expect, UtilityTokenID().StringLE())
|
||||||
assert.Equal(t, expect, tx.Hash().StringLE())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGoverningTokenTX(t *testing.T) {
|
func TestGoverningTokenTX(t *testing.T) {
|
||||||
expect := "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b"
|
expect := "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b"
|
||||||
tx := governingTokenTX()
|
assert.Equal(t, expect, GoverningTokenID().StringLE())
|
||||||
assert.Equal(t, expect, tx.Hash().StringLE())
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/rfc6979"
|
"github.com/nspcc-dev/rfc6979"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -98,10 +99,11 @@ func (p *PrivateKey) Address() string {
|
||||||
return pk.Address()
|
return pk.Address()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signature creates the signature using the private key.
|
// GetScriptHash returns verification script hash for public key associated with
|
||||||
func (p *PrivateKey) Signature() []byte {
|
// the private key.
|
||||||
|
func (p *PrivateKey) GetScriptHash() util.Uint160 {
|
||||||
pk := p.PublicKey()
|
pk := p.PublicKey()
|
||||||
return pk.Signature()
|
return pk.GetScriptHash()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign signs arbitrary length data using the private key.
|
// Sign signs arbitrary length data using the private key.
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
||||||
"github.com/CityOfZion/neo-go/pkg/encoding/address"
|
"github.com/CityOfZion/neo-go/pkg/encoding/address"
|
||||||
"github.com/CityOfZion/neo-go/pkg/io"
|
"github.com/CityOfZion/neo-go/pkg/io"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
"github.com/CityOfZion/neo-go/pkg/vm/opcode"
|
"github.com/CityOfZion/neo-go/pkg/vm/opcode"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
@ -233,18 +234,14 @@ func (p *PublicKey) GetVerificationScript() []byte {
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signature returns a NEO-specific hash of the key.
|
// GetScriptHash returns a Hash160 of verification script for the key.
|
||||||
func (p *PublicKey) Signature() []byte {
|
func (p *PublicKey) GetScriptHash() util.Uint160 {
|
||||||
sig := hash.Hash160(p.GetVerificationScript())
|
return hash.Hash160(p.GetVerificationScript())
|
||||||
|
|
||||||
return sig.BytesBE()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Address returns a base58-encoded NEO-specific address based on the key hash.
|
// Address returns a base58-encoded NEO-specific address based on the key hash.
|
||||||
func (p *PublicKey) Address() string {
|
func (p *PublicKey) Address() string {
|
||||||
sig := hash.Hash160(p.GetVerificationScript())
|
return address.Uint160ToString(p.GetScriptHash())
|
||||||
|
|
||||||
return address.Uint160ToString(sig)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify returns true if the signature is valid and corresponds
|
// Verify returns true if the signature is valid and corresponds
|
||||||
|
|
|
@ -26,6 +26,9 @@ type testChain struct {
|
||||||
blockheight uint32
|
blockheight uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (chain testChain) ApplyPolicyToTxSet([]mempool.TxWithFee) []mempool.TxWithFee {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
func (chain testChain) GetConfig() config.ProtocolConfiguration {
|
func (chain testChain) GetConfig() config.ProtocolConfiguration {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
|
@ -122,7 +125,7 @@ func (chain testChain) GetMemPool() *mempool.Pool {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chain testChain) IsLowPriority(*transaction.Transaction) bool {
|
func (chain testChain) IsLowPriority(util.Fixed8) bool {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ type (
|
||||||
unregister chan peerDrop
|
unregister chan peerDrop
|
||||||
quit chan struct{}
|
quit chan struct{}
|
||||||
|
|
||||||
connected *atomic.Bool
|
consensusStarted *atomic.Bool
|
||||||
|
|
||||||
log *zap.Logger
|
log *zap.Logger
|
||||||
}
|
}
|
||||||
|
@ -88,17 +88,24 @@ func NewServer(config ServerConfig, chain core.Blockchainer, log *zap.Logger) (*
|
||||||
}
|
}
|
||||||
|
|
||||||
s := &Server{
|
s := &Server{
|
||||||
ServerConfig: config,
|
ServerConfig: config,
|
||||||
chain: chain,
|
chain: chain,
|
||||||
id: randomID(),
|
id: randomID(),
|
||||||
quit: make(chan struct{}),
|
quit: make(chan struct{}),
|
||||||
register: make(chan Peer),
|
register: make(chan Peer),
|
||||||
unregister: make(chan peerDrop),
|
unregister: make(chan peerDrop),
|
||||||
peers: make(map[Peer]bool),
|
peers: make(map[Peer]bool),
|
||||||
connected: atomic.NewBool(false),
|
consensusStarted: atomic.NewBool(false),
|
||||||
log: log,
|
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{
|
srv, err := consensus.NewService(consensus.Config{
|
||||||
Logger: log,
|
Logger: log,
|
||||||
|
@ -274,13 +281,13 @@ func (s *Server) runProto() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) tryStartConsensus() {
|
func (s *Server) tryStartConsensus() {
|
||||||
if s.Wallet == nil || s.connected.Load() {
|
if s.Wallet == nil || s.consensusStarted.Load() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.HandshakedPeersCount() >= s.MinPeers {
|
if s.IsInSync() {
|
||||||
s.log.Info("minimum amount of peers were connected to")
|
s.log.Info("node reached synchronized state, starting consensus")
|
||||||
if s.connected.CAS(false, true) {
|
if s.consensusStarted.CAS(false, true) {
|
||||||
s.consensus.Start()
|
s.consensus.Start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -336,6 +343,39 @@ func (s *Server) getVersionMsg() *Message {
|
||||||
return s.MkMsg(CMDVersion, payload)
|
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
|
// When a peer sends out his version we reply with verack after validating
|
||||||
// the version.
|
// the version.
|
||||||
func (s *Server) handleVersionCmd(p Peer, version *payload.Version) error {
|
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 {
|
if t.Type == transaction.MinerType {
|
||||||
return RelayInvalid
|
return RelayInvalid
|
||||||
}
|
}
|
||||||
// TODO: Implement Plugin.CheckPolicy?
|
|
||||||
//if (!Plugin.CheckPolicy(transaction))
|
|
||||||
// return RelayResultReason.PolicyFail;
|
|
||||||
if err := s.chain.PoolTx(t); err != nil {
|
if err := s.chain.PoolTx(t); err != nil {
|
||||||
switch err {
|
switch err {
|
||||||
case core.ErrAlreadyExists:
|
case core.ErrAlreadyExists:
|
||||||
return RelayAlreadyExists
|
return RelayAlreadyExists
|
||||||
case core.ErrOOM:
|
case core.ErrOOM:
|
||||||
return RelayOutOfMemory
|
return RelayOutOfMemory
|
||||||
|
case core.ErrPolicy:
|
||||||
|
return RelayPolicyFail
|
||||||
default:
|
default:
|
||||||
return RelayInvalid
|
return RelayInvalid
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||||
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
"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 txHash util.Uint256
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
gasIDB, _ := hex.DecodeString("602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7")
|
|
||||||
gasID, _ := util.Uint256DecodeBytesLE(gasIDB)
|
|
||||||
|
|
||||||
tx := transaction.NewInvocationTX(script, gas)
|
tx := transaction.NewInvocationTX(script, gas)
|
||||||
|
|
||||||
fromAddress := wif.PrivateKey.Address()
|
fromAddress := wif.PrivateKey.Address()
|
||||||
|
|
||||||
if gas > 0 {
|
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")
|
return txHash, errors.Wrap(err, "failed to add inputs and unspents to transaction")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1
pkg/wallet/testdata/wallet1.json
vendored
Normal file
1
pkg/wallet/testdata/wallet1.json
vendored
Normal 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
1
pkg/wallet/testdata/wallet2.json
vendored
Normal 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}
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -129,3 +130,24 @@ func (w *Wallet) GetAccount(h util.Uint160) *Account {
|
||||||
|
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/encoding/address"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"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)
|
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)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue