Merge pull request #1582 from nspcc-dev/signature_collection/notary_request_payload
network: add notary request payload
This commit is contained in:
commit
3bbf7642ea
24 changed files with 1407 additions and 235 deletions
|
@ -9,6 +9,9 @@ type (
|
||||||
ProtocolConfiguration struct {
|
ProtocolConfiguration struct {
|
||||||
Magic netmode.Magic `yaml:"Magic"`
|
Magic netmode.Magic `yaml:"Magic"`
|
||||||
MemPoolSize int `yaml:"MemPoolSize"`
|
MemPoolSize int `yaml:"MemPoolSize"`
|
||||||
|
// P2PNotaryRequestPayloadPoolSize specifies the memory pool size for P2PNotaryRequestPayloads.
|
||||||
|
// It is valid only if P2PSigExtensions are enabled.
|
||||||
|
P2PNotaryRequestPayloadPoolSize int `yaml:"P2PNotaryRequestPayloadPoolSize"`
|
||||||
// KeepOnlyLatestState specifies if MPT should only store latest state.
|
// KeepOnlyLatestState specifies if MPT should only store latest state.
|
||||||
// If true, DB size will be smaller, but older roots won't be accessible.
|
// If true, DB size will be smaller, but older roots won't be accessible.
|
||||||
// This value should remain the same for the same database.
|
// This value should remain the same for the same database.
|
||||||
|
@ -17,7 +20,7 @@ type (
|
||||||
RemoveUntraceableBlocks bool `yaml:"RemoveUntraceableBlocks"`
|
RemoveUntraceableBlocks bool `yaml:"RemoveUntraceableBlocks"`
|
||||||
// MaxTraceableBlocks is the length of the chain accessible to smart contracts.
|
// MaxTraceableBlocks is the length of the chain accessible to smart contracts.
|
||||||
MaxTraceableBlocks uint32 `yaml:"MaxTraceableBlocks"`
|
MaxTraceableBlocks uint32 `yaml:"MaxTraceableBlocks"`
|
||||||
// P2PSigExtensions enables additional signature-related transaction attributes
|
// P2PSigExtensions enables additional signature-related logic.
|
||||||
P2PSigExtensions bool `yaml:"P2PSigExtensions"`
|
P2PSigExtensions bool `yaml:"P2PSigExtensions"`
|
||||||
// ReservedAttributes allows to have reserved attributes range for experimental or private purposes.
|
// ReservedAttributes allows to have reserved attributes range for experimental or private purposes.
|
||||||
ReservedAttributes bool `yaml:"ReservedAttributes"`
|
ReservedAttributes bool `yaml:"ReservedAttributes"`
|
||||||
|
|
|
@ -418,7 +418,7 @@ func (s *service) verifyBlock(b block.Block) bool {
|
||||||
s.log.Warn("proposed block has already outdated")
|
s.log.Warn("proposed block has already outdated")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
maxBlockSize := int(s.Chain.GetMaxBlockSize())
|
maxBlockSize := int(s.Chain.GetPolicer().GetMaxBlockSize())
|
||||||
size := io.GetVarSize(coreb)
|
size := io.GetVarSize(coreb)
|
||||||
if size > maxBlockSize {
|
if size > maxBlockSize {
|
||||||
s.log.Warn("proposed block size exceeds policy max block size",
|
s.log.Warn("proposed block size exceeds policy max block size",
|
||||||
|
@ -428,7 +428,7 @@ func (s *service) verifyBlock(b block.Block) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
var fee int64
|
var fee int64
|
||||||
var pool = mempool.New(len(coreb.Transactions))
|
var pool = mempool.New(len(coreb.Transactions), 0)
|
||||||
var mainPool = s.Chain.GetMemPool()
|
var mainPool = s.Chain.GetMemPool()
|
||||||
for _, tx := range coreb.Transactions {
|
for _, tx := range coreb.Transactions {
|
||||||
var err error
|
var err error
|
||||||
|
@ -454,7 +454,7 @@ func (s *service) verifyBlock(b block.Block) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
maxBlockSysFee := s.Chain.GetMaxBlockSystemFee()
|
maxBlockSysFee := s.Chain.GetPolicer().GetMaxBlockSystemFee()
|
||||||
if fee > maxBlockSysFee {
|
if fee > maxBlockSysFee {
|
||||||
s.log.Warn("proposed block system fee exceeds policy max block system fee",
|
s.log.Warn("proposed block system fee exceeds policy max block system fee",
|
||||||
zap.Int("max system fee allowed", int(maxBlockSysFee)),
|
zap.Int("max system fee allowed", int(maxBlockSysFee)),
|
||||||
|
|
|
@ -386,7 +386,7 @@ func TestVerifyBlock(t *testing.T) {
|
||||||
require.False(t, srv.verifyBlock(&neoBlock{Block: *b}))
|
require.False(t, srv.verifyBlock(&neoBlock{Block: *b}))
|
||||||
})
|
})
|
||||||
t.Run("bad big size", func(t *testing.T) {
|
t.Run("bad big size", func(t *testing.T) {
|
||||||
script := make([]byte, int(srv.Chain.GetMaxBlockSize()))
|
script := make([]byte, int(srv.Chain.GetPolicer().GetMaxBlockSize()))
|
||||||
script[0] = byte(opcode.RET)
|
script[0] = byte(opcode.RET)
|
||||||
tx := transaction.New(netmode.UnitTestNet, script, 100000)
|
tx := transaction.New(netmode.UnitTestNet, script, 100000)
|
||||||
tx.ValidUntilBlock = 1
|
tx.ValidUntilBlock = 1
|
||||||
|
@ -407,7 +407,7 @@ func TestVerifyBlock(t *testing.T) {
|
||||||
t.Run("bad big sys fee", func(t *testing.T) {
|
t.Run("bad big sys fee", func(t *testing.T) {
|
||||||
txes := make([]*transaction.Transaction, 2)
|
txes := make([]*transaction.Transaction, 2)
|
||||||
for i := range txes {
|
for i := range txes {
|
||||||
txes[i] = transaction.New(netmode.UnitTestNet, []byte{byte(opcode.RET)}, srv.Chain.GetMaxBlockSystemFee()/2+1)
|
txes[i] = transaction.New(netmode.UnitTestNet, []byte{byte(opcode.RET)}, srv.Chain.GetPolicer().GetMaxBlockSystemFee()/2+1)
|
||||||
txes[i].ValidUntilBlock = 1
|
txes[i].ValidUntilBlock = 1
|
||||||
addSender(t, txes[i])
|
addSender(t, txes[i])
|
||||||
signTx(t, srv.Chain.FeePerByte(), txes[i])
|
signTx(t, srv.Chain.FeePerByte(), txes[i])
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/mempool"
|
"github.com/nspcc-dev/neo-go/pkg/core/mempool"
|
||||||
|
@ -42,9 +43,10 @@ const (
|
||||||
headerBatchCount = 2000
|
headerBatchCount = 2000
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
defaultMemPoolSize = 50000
|
defaultMemPoolSize = 50000
|
||||||
defaultMaxTraceableBlocks = 2102400 // 1 year of 15s blocks
|
defaultP2PNotaryRequestPayloadPoolSize = 1000
|
||||||
verificationGasLimit = 100000000 // 1 GAS
|
defaultMaxTraceableBlocks = 2102400 // 1 year of 15s blocks
|
||||||
|
verificationGasLimit = 100000000 // 1 GAS
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -116,6 +118,10 @@ type Blockchain struct {
|
||||||
|
|
||||||
memPool *mempool.Pool
|
memPool *mempool.Pool
|
||||||
|
|
||||||
|
// postBlock is a set of callback methods which should be run under the Blockchain lock after new block is persisted.
|
||||||
|
// Block's transactions are passed via mempool.
|
||||||
|
postBlock []func(blockchainer.Blockchainer, *mempool.Pool, *block.Block)
|
||||||
|
|
||||||
sbCommittee keys.PublicKeys
|
sbCommittee keys.PublicKeys
|
||||||
|
|
||||||
log *zap.Logger
|
log *zap.Logger
|
||||||
|
@ -151,6 +157,10 @@ 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.P2PSigExtensions && cfg.P2PNotaryRequestPayloadPoolSize <= 0 {
|
||||||
|
cfg.P2PNotaryRequestPayloadPoolSize = defaultP2PNotaryRequestPayloadPoolSize
|
||||||
|
log.Info("P2PNotaryRequestPayloadPool size is not set or wrong, setting default value", zap.Int("P2PNotaryRequestPayloadPoolSize", cfg.P2PNotaryRequestPayloadPoolSize))
|
||||||
|
}
|
||||||
if cfg.MaxTraceableBlocks == 0 {
|
if cfg.MaxTraceableBlocks == 0 {
|
||||||
cfg.MaxTraceableBlocks = defaultMaxTraceableBlocks
|
cfg.MaxTraceableBlocks = defaultMaxTraceableBlocks
|
||||||
log.Info("MaxTraceableBlocks is not set or wrong, using default value", zap.Uint32("MaxTraceableBlocks", cfg.MaxTraceableBlocks))
|
log.Info("MaxTraceableBlocks is not set or wrong, using default value", zap.Uint32("MaxTraceableBlocks", cfg.MaxTraceableBlocks))
|
||||||
|
@ -164,7 +174,7 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration, log *zap.L
|
||||||
dao: dao.NewSimple(s, cfg.Magic, cfg.StateRootInHeader),
|
dao: dao.NewSimple(s, cfg.Magic, cfg.StateRootInHeader),
|
||||||
stopCh: make(chan struct{}),
|
stopCh: make(chan struct{}),
|
||||||
runToExitCh: make(chan struct{}),
|
runToExitCh: make(chan struct{}),
|
||||||
memPool: mempool.New(cfg.MemPoolSize),
|
memPool: mempool.New(cfg.MemPoolSize, 0),
|
||||||
sbCommittee: committee,
|
sbCommittee: committee,
|
||||||
log: log,
|
log: log,
|
||||||
events: make(chan bcEvent),
|
events: make(chan bcEvent),
|
||||||
|
@ -452,7 +462,7 @@ func (bc *Blockchain) AddBlock(block *block.Block) error {
|
||||||
if !block.MerkleRoot.Equals(merkle) {
|
if !block.MerkleRoot.Equals(merkle) {
|
||||||
return errors.New("invalid block: MerkleRoot mismatch")
|
return errors.New("invalid block: MerkleRoot mismatch")
|
||||||
}
|
}
|
||||||
mp = mempool.New(len(block.Transactions))
|
mp = mempool.New(len(block.Transactions), 0)
|
||||||
for _, tx := range block.Transactions {
|
for _, tx := range block.Transactions {
|
||||||
var err error
|
var err error
|
||||||
// Transactions are verified before adding them
|
// Transactions are verified before adding them
|
||||||
|
@ -464,7 +474,7 @@ func (bc *Blockchain) AddBlock(block *block.Block) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = bc.verifyAndPoolTx(tx, mp)
|
err = bc.verifyAndPoolTx(tx, mp, bc)
|
||||||
}
|
}
|
||||||
if err != nil && bc.config.VerifyTransactions {
|
if err != nil && bc.config.VerifyTransactions {
|
||||||
return fmt.Errorf("transaction %s failed to verify: %w", tx.Hash().StringLE(), err)
|
return fmt.Errorf("transaction %s failed to verify: %w", tx.Hash().StringLE(), err)
|
||||||
|
@ -720,6 +730,13 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
||||||
bc.lock.Unlock()
|
bc.lock.Unlock()
|
||||||
return fmt.Errorf("failed to call OnPersistEnd for Policy native contract: %w", err)
|
return fmt.Errorf("failed to call OnPersistEnd for Policy native contract: %w", err)
|
||||||
}
|
}
|
||||||
|
if bc.P2PSigExtensionsEnabled() {
|
||||||
|
err := bc.contracts.Notary.OnPersistEnd(bc.dao)
|
||||||
|
if err != nil {
|
||||||
|
bc.lock.Unlock()
|
||||||
|
return fmt.Errorf("failed to call OnPersistEnd for Notary native contract: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
if err := bc.contracts.Designate.OnPersistEnd(bc.dao); err != nil {
|
if err := bc.contracts.Designate.OnPersistEnd(bc.dao); err != nil {
|
||||||
bc.lock.Unlock()
|
bc.lock.Unlock()
|
||||||
return err
|
return err
|
||||||
|
@ -733,7 +750,10 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
||||||
}
|
}
|
||||||
bc.topBlock.Store(block)
|
bc.topBlock.Store(block)
|
||||||
atomic.StoreUint32(&bc.blockHeight, block.Index)
|
atomic.StoreUint32(&bc.blockHeight, block.Index)
|
||||||
bc.memPool.RemoveStale(func(tx *transaction.Transaction) bool { return bc.isTxStillRelevant(tx, txpool) }, bc)
|
bc.memPool.RemoveStale(func(tx *transaction.Transaction) bool { return bc.IsTxStillRelevant(tx, txpool, false) }, bc)
|
||||||
|
for _, f := range bc.postBlock {
|
||||||
|
f(bc, txpool, block)
|
||||||
|
}
|
||||||
bc.lock.Unlock()
|
bc.lock.Unlock()
|
||||||
|
|
||||||
updateBlockHeightMetric(block.Index)
|
updateBlockHeightMetric(block.Index)
|
||||||
|
@ -934,6 +954,24 @@ func (bc *Blockchain) GetGoverningTokenBalance(acc util.Uint160) (*big.Int, uint
|
||||||
return &neo.Balance, neo.LastUpdatedBlock
|
return &neo.Balance, neo.LastUpdatedBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetNotaryBalance returns Notary deposit amount for the specified account.
|
||||||
|
func (bc *Blockchain) GetNotaryBalance(acc util.Uint160) *big.Int {
|
||||||
|
return bc.contracts.Notary.BalanceOf(bc.dao, acc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNotaryContractScriptHash returns Notary native contract hash.
|
||||||
|
func (bc *Blockchain) GetNotaryContractScriptHash() util.Uint160 {
|
||||||
|
if bc.P2PSigExtensionsEnabled() {
|
||||||
|
return bc.contracts.Notary.Hash
|
||||||
|
}
|
||||||
|
return util.Uint160{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNotaryDepositExpiration returns Notary deposit expiration height for the specified account.
|
||||||
|
func (bc *Blockchain) GetNotaryDepositExpiration(acc util.Uint160) uint32 {
|
||||||
|
return bc.contracts.Notary.ExpirationOf(bc.dao, acc)
|
||||||
|
}
|
||||||
|
|
||||||
// LastBatch returns last persisted storage batch.
|
// LastBatch returns last persisted storage batch.
|
||||||
func (bc *Blockchain) LastBatch() *storage.MemBatch {
|
func (bc *Blockchain) LastBatch() *storage.MemBatch {
|
||||||
return bc.lastBatch
|
return bc.lastBatch
|
||||||
|
@ -1204,16 +1242,6 @@ func (bc *Blockchain) FeePerByte() int64 {
|
||||||
return bc.contracts.Policy.GetFeePerByteInternal(bc.dao)
|
return bc.contracts.Policy.GetFeePerByteInternal(bc.dao)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMaxBlockSize returns maximum allowed block size from native Policy contract.
|
|
||||||
func (bc *Blockchain) GetMaxBlockSize() uint32 {
|
|
||||||
return bc.contracts.Policy.GetMaxBlockSizeInternal(bc.dao)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMaxBlockSystemFee returns maximum block system fee from native Policy contract.
|
|
||||||
func (bc *Blockchain) GetMaxBlockSystemFee() int64 {
|
|
||||||
return bc.contracts.Policy.GetMaxBlockSystemFeeInternal(bc.dao)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMemPool returns the memory pool of the blockchain.
|
// GetMemPool returns the memory pool of the blockchain.
|
||||||
func (bc *Blockchain) GetMemPool() *mempool.Pool {
|
func (bc *Blockchain) GetMemPool() *mempool.Pool {
|
||||||
return bc.memPool
|
return bc.memPool
|
||||||
|
@ -1279,9 +1307,10 @@ var (
|
||||||
|
|
||||||
// verifyAndPoolTx verifies whether a transaction is bonafide or not and tries
|
// verifyAndPoolTx verifies whether a transaction is bonafide or not and tries
|
||||||
// to add it to the mempool given.
|
// to add it to the mempool given.
|
||||||
func (bc *Blockchain) verifyAndPoolTx(t *transaction.Transaction, pool *mempool.Pool) error {
|
func (bc *Blockchain) verifyAndPoolTx(t *transaction.Transaction, pool *mempool.Pool, feer mempool.Feer, data ...interface{}) error {
|
||||||
height := bc.BlockHeight()
|
height := bc.BlockHeight()
|
||||||
if t.ValidUntilBlock <= height || t.ValidUntilBlock > height+transaction.MaxValidUntilBlockIncrement {
|
isPartialTx := data != nil
|
||||||
|
if t.ValidUntilBlock <= height || !isPartialTx && t.ValidUntilBlock > height+transaction.MaxValidUntilBlockIncrement {
|
||||||
return fmt.Errorf("%w: ValidUntilBlock = %d, current height = %d", ErrTxExpired, t.ValidUntilBlock, height)
|
return fmt.Errorf("%w: ValidUntilBlock = %d, current height = %d", ErrTxExpired, t.ValidUntilBlock, height)
|
||||||
}
|
}
|
||||||
// Policying.
|
// Policying.
|
||||||
|
@ -1316,14 +1345,14 @@ func (bc *Blockchain) verifyAndPoolTx(t *transaction.Transaction, pool *mempool.
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err := bc.verifyTxWitnesses(t, nil)
|
err := bc.verifyTxWitnesses(t, nil, isPartialTx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := bc.verifyTxAttributes(t); err != nil {
|
if err := bc.verifyTxAttributes(t, isPartialTx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = pool.Add(t, bc)
|
err = pool.Add(t, feer, data...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, mempool.ErrConflict):
|
case errors.Is(err, mempool.ErrConflict):
|
||||||
|
@ -1344,7 +1373,7 @@ func (bc *Blockchain) verifyAndPoolTx(t *transaction.Transaction, pool *mempool.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc *Blockchain) verifyTxAttributes(tx *transaction.Transaction) error {
|
func (bc *Blockchain) verifyTxAttributes(tx *transaction.Transaction, isPartialTx bool) error {
|
||||||
for i := range tx.Attributes {
|
for i := range tx.Attributes {
|
||||||
switch attrType := tx.Attributes[i].Type; attrType {
|
switch attrType := tx.Attributes[i].Type; attrType {
|
||||||
case transaction.HighPriority:
|
case transaction.HighPriority:
|
||||||
|
@ -1384,9 +1413,19 @@ func (bc *Blockchain) verifyTxAttributes(tx *transaction.Transaction) error {
|
||||||
if !bc.config.P2PSigExtensions {
|
if !bc.config.P2PSigExtensions {
|
||||||
return fmt.Errorf("%w: NotValidBefore attribute was found, but P2PSigExtensions are disabled", ErrInvalidAttribute)
|
return fmt.Errorf("%w: NotValidBefore attribute was found, but P2PSigExtensions are disabled", ErrInvalidAttribute)
|
||||||
}
|
}
|
||||||
nvb := tx.Attributes[i].Value.(*transaction.NotValidBefore)
|
nvb := tx.Attributes[i].Value.(*transaction.NotValidBefore).Height
|
||||||
if height := bc.BlockHeight(); height < nvb.Height {
|
if isPartialTx {
|
||||||
return fmt.Errorf("%w: transaction is not yet valid: NotValidBefore = %d, current height = %d", ErrInvalidAttribute, nvb.Height, height)
|
maxNVBDelta := bc.contracts.Notary.GetMaxNotValidBeforeDelta(bc.dao)
|
||||||
|
if bc.BlockHeight()+maxNVBDelta < nvb {
|
||||||
|
return fmt.Errorf("%w: partially-filled transaction should become valid not less then %d blocks after current chain's height %d", ErrInvalidAttribute, maxNVBDelta, bc.BlockHeight())
|
||||||
|
}
|
||||||
|
if nvb+maxNVBDelta < tx.ValidUntilBlock {
|
||||||
|
return fmt.Errorf("%w: partially-filled transaction should be valid during less than %d blocks", ErrInvalidAttribute, maxNVBDelta)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if height := bc.BlockHeight(); height < nvb {
|
||||||
|
return fmt.Errorf("%w: transaction is not yet valid: NotValidBefore = %d, current height = %d", ErrInvalidAttribute, nvb, height)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case transaction.ConflictsT:
|
case transaction.ConflictsT:
|
||||||
if !bc.config.P2PSigExtensions {
|
if !bc.config.P2PSigExtensions {
|
||||||
|
@ -1412,13 +1451,13 @@ func (bc *Blockchain) verifyTxAttributes(tx *transaction.Transaction) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// isTxStillRelevant is a callback for mempool transaction filtering after the
|
// IsTxStillRelevant is a callback for mempool transaction filtering after the
|
||||||
// new block addition. It returns false for transactions added by the new block
|
// new block addition. It returns false for transactions added by the new block
|
||||||
// (passed via txpool) and does witness reverification for non-standard
|
// (passed via txpool) and does witness reverification for non-standard
|
||||||
// contracts. It operates under the assumption that full transaction verification
|
// contracts. It operates under the assumption that full transaction verification
|
||||||
// was already done so we don't need to check basic things like size, input/output
|
// was already done so we don't need to check basic things like size, input/output
|
||||||
// correctness, presence in blocks before the new one, etc.
|
// correctness, presence in blocks before the new one, etc.
|
||||||
func (bc *Blockchain) isTxStillRelevant(t *transaction.Transaction, txpool *mempool.Pool) bool {
|
func (bc *Blockchain) IsTxStillRelevant(t *transaction.Transaction, txpool *mempool.Pool, isPartialTx bool) bool {
|
||||||
var recheckWitness bool
|
var recheckWitness bool
|
||||||
var curheight = bc.BlockHeight()
|
var curheight = bc.BlockHeight()
|
||||||
|
|
||||||
|
@ -1432,7 +1471,7 @@ func (bc *Blockchain) isTxStillRelevant(t *transaction.Transaction, txpool *memp
|
||||||
} else if txpool.HasConflicts(t, bc) {
|
} else if txpool.HasConflicts(t, bc) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if err := bc.verifyTxAttributes(t); err != nil {
|
if err := bc.verifyTxAttributes(t, isPartialTx); err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for i := range t.Scripts {
|
for i := range t.Scripts {
|
||||||
|
@ -1442,7 +1481,7 @@ func (bc *Blockchain) isTxStillRelevant(t *transaction.Transaction, txpool *memp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if recheckWitness {
|
if recheckWitness {
|
||||||
return bc.verifyTxWitnesses(t, nil) == nil
|
return bc.verifyTxWitnesses(t, nil, isPartialTx) == nil
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
@ -1525,10 +1564,10 @@ func (bc *Blockchain) verifyStateRootWitness(r *state.MPTRoot) error {
|
||||||
// current blockchain state. Note that this verification is completely isolated
|
// current blockchain state. Note that this verification is completely isolated
|
||||||
// from the main node's mempool.
|
// from the main node's mempool.
|
||||||
func (bc *Blockchain) VerifyTx(t *transaction.Transaction) error {
|
func (bc *Blockchain) VerifyTx(t *transaction.Transaction) error {
|
||||||
var mp = mempool.New(1)
|
var mp = mempool.New(1, 0)
|
||||||
bc.lock.RLock()
|
bc.lock.RLock()
|
||||||
defer bc.lock.RUnlock()
|
defer bc.lock.RUnlock()
|
||||||
return bc.verifyAndPoolTx(t, mp)
|
return bc.verifyAndPoolTx(t, mp, bc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PoolTx verifies and tries to add given transaction into the mempool. If not
|
// PoolTx verifies and tries to add given transaction into the mempool. If not
|
||||||
|
@ -1545,7 +1584,21 @@ func (bc *Blockchain) PoolTx(t *transaction.Transaction, pools ...*mempool.Pool)
|
||||||
if len(pools) == 1 {
|
if len(pools) == 1 {
|
||||||
pool = pools[0]
|
pool = pools[0]
|
||||||
}
|
}
|
||||||
return bc.verifyAndPoolTx(t, pool)
|
return bc.verifyAndPoolTx(t, pool, bc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PoolTxWithData verifies and tries to add given transaction with additional data into the mempool.
|
||||||
|
func (bc *Blockchain) PoolTxWithData(t *transaction.Transaction, data interface{}, mp *mempool.Pool, feer mempool.Feer, verificationFunction func(bc blockchainer.Blockchainer, tx *transaction.Transaction, data interface{}) error) error {
|
||||||
|
bc.lock.RLock()
|
||||||
|
defer bc.lock.RUnlock()
|
||||||
|
|
||||||
|
if verificationFunction != nil {
|
||||||
|
err := verificationFunction(bc, t, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bc.verifyAndPoolTx(t, mp, feer, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
//GetStandByValidators returns validators from the configuration.
|
//GetStandByValidators returns validators from the configuration.
|
||||||
|
@ -1595,6 +1648,7 @@ var (
|
||||||
ErrWitnessHashMismatch = errors.New("witness hash mismatch")
|
ErrWitnessHashMismatch = errors.New("witness hash mismatch")
|
||||||
ErrNativeContractWitness = errors.New("native contract witness must have empty verification script")
|
ErrNativeContractWitness = errors.New("native contract witness must have empty verification script")
|
||||||
ErrVerificationFailed = errors.New("signature check failed")
|
ErrVerificationFailed = errors.New("signature check failed")
|
||||||
|
ErrInvalidSignature = fmt.Errorf("%w: invalid signature", ErrVerificationFailed)
|
||||||
ErrUnknownVerificationContract = errors.New("unknown verification contract")
|
ErrUnknownVerificationContract = errors.New("unknown verification contract")
|
||||||
ErrInvalidVerificationContract = errors.New("verification contract is missing `verify` method")
|
ErrInvalidVerificationContract = errors.New("verification contract is missing `verify` method")
|
||||||
)
|
)
|
||||||
|
@ -1670,12 +1724,12 @@ func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transa
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("%w: invalid return value", ErrVerificationFailed)
|
return 0, fmt.Errorf("%w: invalid return value", ErrVerificationFailed)
|
||||||
}
|
}
|
||||||
if !res {
|
|
||||||
return 0, fmt.Errorf("%w: invalid signature", ErrVerificationFailed)
|
|
||||||
}
|
|
||||||
if vm.Estack().Len() != 0 {
|
if vm.Estack().Len() != 0 {
|
||||||
return 0, fmt.Errorf("%w: expected exactly one returned value", ErrVerificationFailed)
|
return 0, fmt.Errorf("%w: expected exactly one returned value", ErrVerificationFailed)
|
||||||
}
|
}
|
||||||
|
if !res {
|
||||||
|
return vm.GasConsumed(), ErrInvalidSignature
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return 0, fmt.Errorf("%w: no result returned from the script", ErrVerificationFailed)
|
return 0, fmt.Errorf("%w: no result returned from the script", ErrVerificationFailed)
|
||||||
}
|
}
|
||||||
|
@ -1688,15 +1742,23 @@ func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transa
|
||||||
// is used for easy interop access and can be omitted for transactions that are
|
// is used for easy interop access and can be omitted for transactions that are
|
||||||
// not yet added into any block.
|
// not yet added into any block.
|
||||||
// Golang implementation of VerifyWitnesses method in C# (https://github.com/neo-project/neo/blob/master/neo/SmartContract/Helper.cs#L87).
|
// Golang implementation of VerifyWitnesses method in C# (https://github.com/neo-project/neo/blob/master/neo/SmartContract/Helper.cs#L87).
|
||||||
func (bc *Blockchain) verifyTxWitnesses(t *transaction.Transaction, block *block.Block) error {
|
func (bc *Blockchain) verifyTxWitnesses(t *transaction.Transaction, block *block.Block, isPartialTx bool) error {
|
||||||
if len(t.Signers) != len(t.Scripts) {
|
if len(t.Signers) != len(t.Scripts) {
|
||||||
return fmt.Errorf("%w: %d vs %d", ErrTxInvalidWitnessNum, len(t.Signers), len(t.Scripts))
|
return fmt.Errorf("%w: %d vs %d", ErrTxInvalidWitnessNum, len(t.Signers), len(t.Scripts))
|
||||||
}
|
}
|
||||||
interopCtx := bc.newInteropContext(trigger.Verification, bc.dao, block, t)
|
interopCtx := bc.newInteropContext(trigger.Verification, bc.dao, block, t)
|
||||||
gasLimit := t.NetworkFee - int64(t.Size())*bc.FeePerByte()
|
gasLimit := t.NetworkFee - int64(t.Size())*bc.FeePerByte()
|
||||||
|
if bc.P2PSigExtensionsEnabled() {
|
||||||
|
attrs := t.GetAttributes(transaction.NotaryAssistedT)
|
||||||
|
if len(attrs) != 0 {
|
||||||
|
na := attrs[0].Value.(*transaction.NotaryAssisted)
|
||||||
|
gasLimit -= (int64(na.NKeys) + 1) * transaction.NotaryServiceFeePerKey
|
||||||
|
}
|
||||||
|
}
|
||||||
for i := range t.Signers {
|
for i := range t.Signers {
|
||||||
gasConsumed, err := bc.verifyHashAgainstScript(t.Signers[i].Account, &t.Scripts[i], interopCtx, gasLimit)
|
gasConsumed, err := bc.verifyHashAgainstScript(t.Signers[i].Account, &t.Scripts[i], interopCtx, gasLimit)
|
||||||
if err != nil {
|
if err != nil &&
|
||||||
|
!(i == 0 && isPartialTx && errors.Is(err, ErrInvalidSignature)) { // it's OK for partially-filled transaction with dummy first witness.
|
||||||
return fmt.Errorf("witness #%d: %w", i, err)
|
return fmt.Errorf("witness #%d: %w", i, err)
|
||||||
}
|
}
|
||||||
gasLimit -= gasConsumed
|
gasLimit -= gasConsumed
|
||||||
|
@ -1749,3 +1811,33 @@ func (bc *Blockchain) newInteropContext(trigger trigger.Type, d dao.DAO, block *
|
||||||
func (bc *Blockchain) P2PSigExtensionsEnabled() bool {
|
func (bc *Blockchain) P2PSigExtensionsEnabled() bool {
|
||||||
return bc.config.P2PSigExtensions
|
return bc.config.P2PSigExtensions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegisterPostBlock appends provided function to the list of functions which should be run after new block
|
||||||
|
// is stored.
|
||||||
|
func (bc *Blockchain) RegisterPostBlock(f func(blockchainer.Blockchainer, *mempool.Pool, *block.Block)) {
|
||||||
|
bc.postBlock = append(bc.postBlock, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- start Policer.
|
||||||
|
|
||||||
|
// GetPolicer provides access to policy values via Policer interface.
|
||||||
|
func (bc *Blockchain) GetPolicer() blockchainer.Policer {
|
||||||
|
return bc
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMaxBlockSize returns maximum allowed block size from native Policy contract.
|
||||||
|
func (bc *Blockchain) GetMaxBlockSize() uint32 {
|
||||||
|
return bc.contracts.Policy.GetMaxBlockSizeInternal(bc.dao)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMaxBlockSystemFee returns maximum block system fee from native Policy contract.
|
||||||
|
func (bc *Blockchain) GetMaxBlockSystemFee() int64 {
|
||||||
|
return bc.contracts.Policy.GetMaxBlockSystemFeeInternal(bc.dao)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMaxVerificationGAS returns maximum verification GAS Policy limit.
|
||||||
|
func (bc *Blockchain) GetMaxVerificationGAS() int64 {
|
||||||
|
return bc.contracts.Policy.GetMaxVerificationGas(bc.dao)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- end Policer.
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/chaindump"
|
"github.com/nspcc-dev/neo-go/pkg/core/chaindump"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/fee"
|
"github.com/nspcc-dev/neo-go/pkg/core/fee"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
||||||
|
@ -447,7 +448,7 @@ func TestVerifyTx(t *testing.T) {
|
||||||
require.True(t, errors.Is(err, ErrAlreadyExists))
|
require.True(t, errors.Is(err, ErrAlreadyExists))
|
||||||
})
|
})
|
||||||
t.Run("MemPoolOOM", func(t *testing.T) {
|
t.Run("MemPoolOOM", func(t *testing.T) {
|
||||||
bc.memPool = mempool.New(1)
|
bc.memPool = mempool.New(1, 0)
|
||||||
tx1 := bc.newTestTx(h, testScript)
|
tx1 := bc.newTestTx(h, testScript)
|
||||||
tx1.NetworkFee += 10000 // Give it more priority.
|
tx1.NetworkFee += 10000 // Give it more priority.
|
||||||
require.NoError(t, accs[0].SignTx(tx1))
|
require.NoError(t, accs[0].SignTx(tx1))
|
||||||
|
@ -926,6 +927,96 @@ func TestVerifyTx(t *testing.T) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
t.Run("Partially-filled transaction", func(t *testing.T) {
|
||||||
|
bc.config.P2PSigExtensions = true
|
||||||
|
getPartiallyFilledTx := func(nvb uint32, validUntil uint32) *transaction.Transaction {
|
||||||
|
tx := bc.newTestTx(h, testScript)
|
||||||
|
tx.ValidUntilBlock = validUntil
|
||||||
|
tx.Attributes = []transaction.Attribute{
|
||||||
|
{
|
||||||
|
Type: transaction.NotValidBeforeT,
|
||||||
|
Value: &transaction.NotValidBefore{Height: nvb},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: transaction.NotaryAssistedT,
|
||||||
|
Value: &transaction.NotaryAssisted{NKeys: 0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tx.Signers = []transaction.Signer{
|
||||||
|
{
|
||||||
|
Account: bc.contracts.Notary.Hash,
|
||||||
|
Scopes: transaction.None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Account: testchain.MultisigScriptHash(),
|
||||||
|
Scopes: transaction.None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
size := io.GetVarSize(tx)
|
||||||
|
netFee, sizeDelta := fee.Calculate(testchain.MultisigVerificationScript())
|
||||||
|
tx.NetworkFee = netFee + // multisig witness verification price
|
||||||
|
int64(size)*bc.FeePerByte() + // fee for unsigned size
|
||||||
|
int64(sizeDelta)*bc.FeePerByte() + //fee for multisig size
|
||||||
|
66*bc.FeePerByte() + // fee for Notary signature size (66 bytes for Invocation script and 0 bytes for Verification script)
|
||||||
|
2*bc.FeePerByte() + // fee for the length of each script in Notary witness (they are nil, so we did not take them into account during `size` calculation)
|
||||||
|
transaction.NotaryServiceFeePerKey + // fee for Notary attribute
|
||||||
|
fee.Opcode( // Notary verification script
|
||||||
|
opcode.PUSHDATA1, opcode.RET, // invocation script
|
||||||
|
opcode.DEPTH, opcode.PACK, opcode.PUSHDATA1, opcode.RET, // arguments for native verification call
|
||||||
|
opcode.PUSHDATA1, opcode.SYSCALL, opcode.RET) + // Neo.Native.Call
|
||||||
|
native.NotaryVerificationPrice // Notary witness verification price
|
||||||
|
tx.Scripts = []transaction.Witness{
|
||||||
|
{
|
||||||
|
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, make([]byte, 64, 64)...),
|
||||||
|
VerificationScript: []byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
InvocationScript: testchain.Sign(tx.GetSignedPart()),
|
||||||
|
VerificationScript: testchain.MultisigVerificationScript(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return tx
|
||||||
|
}
|
||||||
|
|
||||||
|
mp := mempool.New(10, 1)
|
||||||
|
verificationF := func(bc blockchainer.Blockchainer, tx *transaction.Transaction, data interface{}) error {
|
||||||
|
if data.(int) > 5 {
|
||||||
|
return errors.New("bad data")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
t.Run("failed pre-verification", func(t *testing.T) {
|
||||||
|
tx := getPartiallyFilledTx(bc.blockHeight, bc.blockHeight+1)
|
||||||
|
require.Error(t, bc.PoolTxWithData(tx, 6, mp, bc, verificationF)) // here and below let's use `bc` instead of proper NotaryFeer for the test simplicity.
|
||||||
|
})
|
||||||
|
t.Run("GasLimitExceeded during witness verification", func(t *testing.T) {
|
||||||
|
tx := getPartiallyFilledTx(bc.blockHeight, bc.blockHeight+1)
|
||||||
|
tx.NetworkFee-- // to check that NetworkFee was set correctly in getPartiallyFilledTx
|
||||||
|
tx.Scripts = []transaction.Witness{
|
||||||
|
{
|
||||||
|
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, make([]byte, 64, 64)...),
|
||||||
|
VerificationScript: []byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
InvocationScript: testchain.Sign(tx.GetSignedPart()),
|
||||||
|
VerificationScript: testchain.MultisigVerificationScript(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
require.Error(t, bc.PoolTxWithData(tx, 5, mp, bc, verificationF))
|
||||||
|
})
|
||||||
|
t.Run("bad NVB: too big", func(t *testing.T) {
|
||||||
|
tx := getPartiallyFilledTx(bc.blockHeight+bc.contracts.Notary.GetMaxNotValidBeforeDelta(bc.dao)+1, bc.blockHeight+1)
|
||||||
|
require.True(t, errors.Is(bc.PoolTxWithData(tx, 5, mp, bc, verificationF), ErrInvalidAttribute))
|
||||||
|
})
|
||||||
|
t.Run("bad ValidUntilBlock: too small", func(t *testing.T) {
|
||||||
|
tx := getPartiallyFilledTx(bc.blockHeight, bc.blockHeight+bc.contracts.Notary.GetMaxNotValidBeforeDelta(bc.dao)+1)
|
||||||
|
require.True(t, errors.Is(bc.PoolTxWithData(tx, 5, mp, bc, verificationF), ErrInvalidAttribute))
|
||||||
|
})
|
||||||
|
t.Run("good", func(t *testing.T) {
|
||||||
|
tx := getPartiallyFilledTx(bc.blockHeight, bc.blockHeight+1)
|
||||||
|
require.NoError(t, bc.PoolTxWithData(tx, 5, mp, bc, verificationF))
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVerifyHashAgainstScript(t *testing.T) {
|
func TestVerifyHashAgainstScript(t *testing.T) {
|
||||||
|
@ -1021,9 +1112,9 @@ func TestIsTxStillRelevant(t *testing.T) {
|
||||||
tx := newTx(t)
|
tx := newTx(t)
|
||||||
require.NoError(t, testchain.SignTx(bc, tx))
|
require.NoError(t, testchain.SignTx(bc, tx))
|
||||||
|
|
||||||
require.True(t, bc.isTxStillRelevant(tx, nil))
|
require.True(t, bc.IsTxStillRelevant(tx, nil, false))
|
||||||
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||||
require.False(t, bc.isTxStillRelevant(tx, nil))
|
require.False(t, bc.IsTxStillRelevant(tx, nil, false))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("tx is already persisted", func(t *testing.T) {
|
t.Run("tx is already persisted", func(t *testing.T) {
|
||||||
|
@ -1031,9 +1122,9 @@ func TestIsTxStillRelevant(t *testing.T) {
|
||||||
tx.ValidUntilBlock = bc.BlockHeight() + 2
|
tx.ValidUntilBlock = bc.BlockHeight() + 2
|
||||||
require.NoError(t, testchain.SignTx(bc, tx))
|
require.NoError(t, testchain.SignTx(bc, tx))
|
||||||
|
|
||||||
require.True(t, bc.isTxStillRelevant(tx, nil))
|
require.True(t, bc.IsTxStillRelevant(tx, nil, false))
|
||||||
require.NoError(t, bc.AddBlock(bc.newBlock(tx)))
|
require.NoError(t, bc.AddBlock(bc.newBlock(tx)))
|
||||||
require.False(t, bc.isTxStillRelevant(tx, nil))
|
require.False(t, bc.IsTxStillRelevant(tx, nil, false))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("tx with Conflicts attribute", func(t *testing.T) {
|
t.Run("tx with Conflicts attribute", func(t *testing.T) {
|
||||||
|
@ -1047,9 +1138,9 @@ func TestIsTxStillRelevant(t *testing.T) {
|
||||||
}}
|
}}
|
||||||
require.NoError(t, testchain.SignTx(bc, tx2))
|
require.NoError(t, testchain.SignTx(bc, tx2))
|
||||||
|
|
||||||
require.True(t, bc.isTxStillRelevant(tx1, mp))
|
require.True(t, bc.IsTxStillRelevant(tx1, mp, false))
|
||||||
require.NoError(t, bc.verifyAndPoolTx(tx2, mp))
|
require.NoError(t, bc.verifyAndPoolTx(tx2, mp, bc))
|
||||||
require.False(t, bc.isTxStillRelevant(tx1, mp))
|
require.False(t, bc.IsTxStillRelevant(tx1, mp, false))
|
||||||
})
|
})
|
||||||
t.Run("NotValidBefore", func(t *testing.T) {
|
t.Run("NotValidBefore", func(t *testing.T) {
|
||||||
tx3 := newTx(t)
|
tx3 := newTx(t)
|
||||||
|
@ -1060,9 +1151,9 @@ func TestIsTxStillRelevant(t *testing.T) {
|
||||||
tx3.ValidUntilBlock = bc.BlockHeight() + 2
|
tx3.ValidUntilBlock = bc.BlockHeight() + 2
|
||||||
require.NoError(t, testchain.SignTx(bc, tx3))
|
require.NoError(t, testchain.SignTx(bc, tx3))
|
||||||
|
|
||||||
require.False(t, bc.isTxStillRelevant(tx3, nil))
|
require.False(t, bc.IsTxStillRelevant(tx3, nil, false))
|
||||||
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||||
require.True(t, bc.isTxStillRelevant(tx3, nil))
|
require.True(t, bc.IsTxStillRelevant(tx3, nil, false))
|
||||||
})
|
})
|
||||||
t.Run("contract witness check fails", func(t *testing.T) {
|
t.Run("contract witness check fails", func(t *testing.T) {
|
||||||
src := fmt.Sprintf(`package verify
|
src := fmt.Sprintf(`package verify
|
||||||
|
@ -1087,9 +1178,9 @@ func TestIsTxStillRelevant(t *testing.T) {
|
||||||
require.NoError(t, testchain.SignTx(bc, tx))
|
require.NoError(t, testchain.SignTx(bc, tx))
|
||||||
tx.Scripts = append(tx.Scripts, transaction.Witness{})
|
tx.Scripts = append(tx.Scripts, transaction.Witness{})
|
||||||
|
|
||||||
require.True(t, bc.isTxStillRelevant(tx, mp))
|
require.True(t, bc.IsTxStillRelevant(tx, mp, false))
|
||||||
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||||
require.False(t, bc.isTxStillRelevant(tx, mp))
|
require.False(t, bc.IsTxStillRelevant(tx, mp, false))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ type Blockchainer interface {
|
||||||
AddStateRoot(r *state.MPTRoot) error
|
AddStateRoot(r *state.MPTRoot) error
|
||||||
CalculateClaimable(h util.Uint160, endHeight uint32) (*big.Int, error)
|
CalculateClaimable(h util.Uint160, endHeight uint32) (*big.Int, error)
|
||||||
Close()
|
Close()
|
||||||
|
IsTxStillRelevant(t *transaction.Transaction, txpool *mempool.Pool, isPartialTx bool) bool
|
||||||
HeaderHeight() uint32
|
HeaderHeight() uint32
|
||||||
GetBlock(hash util.Uint256) (*block.Block, error)
|
GetBlock(hash util.Uint256) (*block.Block, error)
|
||||||
GetCommittee() (keys.PublicKeys, error)
|
GetCommittee() (keys.PublicKeys, error)
|
||||||
|
@ -40,9 +41,13 @@ type Blockchainer interface {
|
||||||
HasBlock(util.Uint256) bool
|
HasBlock(util.Uint256) bool
|
||||||
HasTransaction(util.Uint256) bool
|
HasTransaction(util.Uint256) bool
|
||||||
GetAppExecResults(util.Uint256, trigger.Type) ([]state.AppExecResult, error)
|
GetAppExecResults(util.Uint256, trigger.Type) ([]state.AppExecResult, error)
|
||||||
|
GetNotaryDepositExpiration(acc util.Uint160) uint32
|
||||||
GetNativeContractScriptHash(string) (util.Uint160, error)
|
GetNativeContractScriptHash(string) (util.Uint160, error)
|
||||||
GetNextBlockValidators() ([]*keys.PublicKey, error)
|
GetNextBlockValidators() ([]*keys.PublicKey, error)
|
||||||
GetNEP17Balances(util.Uint160) *state.NEP17Balances
|
GetNEP17Balances(util.Uint160) *state.NEP17Balances
|
||||||
|
GetNotaryContractScriptHash() util.Uint160
|
||||||
|
GetNotaryBalance(acc util.Uint160) *big.Int
|
||||||
|
GetPolicer() Policer
|
||||||
GetValidators() ([]*keys.PublicKey, error)
|
GetValidators() ([]*keys.PublicKey, error)
|
||||||
GetStandByCommittee() keys.PublicKeys
|
GetStandByCommittee() keys.PublicKeys
|
||||||
GetStandByValidators() keys.PublicKeys
|
GetStandByValidators() keys.PublicKeys
|
||||||
|
@ -53,9 +58,9 @@ type Blockchainer interface {
|
||||||
GetTestVM(tx *transaction.Transaction, b *block.Block) *vm.VM
|
GetTestVM(tx *transaction.Transaction, b *block.Block) *vm.VM
|
||||||
GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error)
|
GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error)
|
||||||
mempool.Feer // fee interface
|
mempool.Feer // fee interface
|
||||||
GetMaxBlockSize() uint32
|
|
||||||
GetMaxBlockSystemFee() int64
|
|
||||||
PoolTx(t *transaction.Transaction, pools ...*mempool.Pool) error
|
PoolTx(t *transaction.Transaction, pools ...*mempool.Pool) error
|
||||||
|
PoolTxWithData(t *transaction.Transaction, data interface{}, mp *mempool.Pool, feer mempool.Feer, verificationFunction func(bc Blockchainer, t *transaction.Transaction, data interface{}) error) error
|
||||||
|
RegisterPostBlock(f func(Blockchainer, *mempool.Pool, *block.Block))
|
||||||
SubscribeForBlocks(ch chan<- *block.Block)
|
SubscribeForBlocks(ch chan<- *block.Block)
|
||||||
SubscribeForExecutions(ch chan<- *state.AppExecResult)
|
SubscribeForExecutions(ch chan<- *state.AppExecResult)
|
||||||
SubscribeForNotifications(ch chan<- *state.NotificationEvent)
|
SubscribeForNotifications(ch chan<- *state.NotificationEvent)
|
||||||
|
|
8
pkg/core/blockchainer/policer.go
Normal file
8
pkg/core/blockchainer/policer.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package blockchainer
|
||||||
|
|
||||||
|
// Policer is an interface that abstracts the implementation of policy methods.
|
||||||
|
type Policer interface {
|
||||||
|
GetMaxBlockSize() uint32
|
||||||
|
GetMaxBlockSystemFee() int64
|
||||||
|
GetMaxVerificationGAS() int64
|
||||||
|
}
|
|
@ -439,6 +439,38 @@ func invokeContractMethod(chain *Blockchain, sysfee int64, hash util.Uint160, me
|
||||||
return &res[0], nil
|
return &res[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func invokeContractMethodBy(t *testing.T, chain *Blockchain, signer *wallet.Account, hash util.Uint160, method string, args ...interface{}) (*state.AppExecResult, error) {
|
||||||
|
var (
|
||||||
|
netfee int64 = 1000_0000
|
||||||
|
sysfee int64 = 1_0000_0000
|
||||||
|
)
|
||||||
|
transferTx := transferTokenFromMultisigAccount(t, chain, signer.PrivateKey().PublicKey().GetScriptHash(), chain.contracts.GAS.Hash, sysfee+netfee+1000_0000, nil)
|
||||||
|
res, err := chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, vm.HaltState, res[0].VMState)
|
||||||
|
require.Equal(t, 0, len(res[0].Stack))
|
||||||
|
|
||||||
|
w := io.NewBufBinWriter()
|
||||||
|
emit.AppCallWithOperationAndArgs(w.BinWriter, hash, method, args...)
|
||||||
|
if w.Err != nil {
|
||||||
|
return nil, w.Err
|
||||||
|
}
|
||||||
|
script := w.Bytes()
|
||||||
|
tx := transaction.New(chain.GetConfig().Magic, script, sysfee)
|
||||||
|
tx.ValidUntilBlock = chain.blockHeight + 1
|
||||||
|
tx.Signers = []transaction.Signer{
|
||||||
|
{Account: signer.PrivateKey().PublicKey().GetScriptHash()},
|
||||||
|
}
|
||||||
|
tx.NetworkFee = netfee
|
||||||
|
err = signer.SignTx(tx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, chain.AddBlock(chain.newBlock(tx)))
|
||||||
|
|
||||||
|
res, err = chain.GetAppExecResults(tx.Hash(), trigger.Application)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return &res[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
func transferTokenFromMultisigAccount(t *testing.T, chain *Blockchain, to, tokenHash util.Uint160, amount int64, additionalArgs ...interface{}) *transaction.Transaction {
|
func transferTokenFromMultisigAccount(t *testing.T, chain *Blockchain, to, tokenHash util.Uint160, amount int64, additionalArgs ...interface{}) *transaction.Transaction {
|
||||||
transferTx := newNEP17Transfer(tokenHash, testchain.MultisigScriptHash(), to, amount, additionalArgs...)
|
transferTx := newNEP17Transfer(tokenHash, testchain.MultisigScriptHash(), to, amount, additionalArgs...)
|
||||||
transferTx.SystemFee = 100000000
|
transferTx.SystemFee = 100000000
|
||||||
|
@ -451,7 +483,7 @@ func transferTokenFromMultisigAccount(t *testing.T, chain *Blockchain, to, token
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkResult(t *testing.T, result *state.AppExecResult, expected stackitem.Item) {
|
func checkResult(t *testing.T, result *state.AppExecResult, expected stackitem.Item) {
|
||||||
require.Equal(t, vm.HaltState, result.VMState)
|
require.Equal(t, vm.HaltState, result.VMState, result.FaultException)
|
||||||
require.Equal(t, 1, len(result.Stack))
|
require.Equal(t, 1, len(result.Stack))
|
||||||
require.Equal(t, expected, result.Stack[0])
|
require.Equal(t, expected, result.Stack[0])
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ var (
|
||||||
type item struct {
|
type item struct {
|
||||||
txn *transaction.Transaction
|
txn *transaction.Transaction
|
||||||
blockStamp uint32
|
blockStamp uint32
|
||||||
|
data interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// items is a slice of item.
|
// items is a slice of item.
|
||||||
|
@ -64,9 +65,10 @@ type Pool struct {
|
||||||
|
|
||||||
capacity int
|
capacity int
|
||||||
feePerByte int64
|
feePerByte int64
|
||||||
|
payerIndex int
|
||||||
|
|
||||||
resendThreshold uint32
|
resendThreshold uint32
|
||||||
resendFunc func(*transaction.Transaction)
|
resendFunc func(*transaction.Transaction, interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p items) Len() int { return len(p) }
|
func (p items) Len() int { return len(p) }
|
||||||
|
@ -149,11 +151,12 @@ func (mp *Pool) HasConflicts(t *transaction.Transaction, fee Feer) bool {
|
||||||
// tryAddSendersFee tries to add system fee and network fee to the total sender`s fee in mempool
|
// tryAddSendersFee tries to add system fee and network fee to the total sender`s fee in mempool
|
||||||
// and returns false if both balance check is required and sender has not enough GAS to pay
|
// and returns false if both balance check is required and sender has not enough GAS to pay
|
||||||
func (mp *Pool) tryAddSendersFee(tx *transaction.Transaction, feer Feer, needCheck bool) bool {
|
func (mp *Pool) tryAddSendersFee(tx *transaction.Transaction, feer Feer, needCheck bool) bool {
|
||||||
senderFee, ok := mp.fees[tx.Sender()]
|
payer := tx.Signers[mp.payerIndex].Account
|
||||||
|
senderFee, ok := mp.fees[payer]
|
||||||
if !ok {
|
if !ok {
|
||||||
senderFee.balance = feer.GetUtilityTokenBalance(tx.Sender())
|
senderFee.balance = feer.GetUtilityTokenBalance(payer)
|
||||||
senderFee.feeSum = big.NewInt(0)
|
senderFee.feeSum = big.NewInt(0)
|
||||||
mp.fees[tx.Sender()] = senderFee
|
mp.fees[payer] = senderFee
|
||||||
}
|
}
|
||||||
if needCheck {
|
if needCheck {
|
||||||
newFeeSum, err := checkBalance(tx, senderFee)
|
newFeeSum, err := checkBalance(tx, senderFee)
|
||||||
|
@ -182,11 +185,14 @@ func checkBalance(tx *transaction.Transaction, balance utilityBalanceAndFees) (*
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add tries to add given transaction to the Pool.
|
// Add tries to add given transaction to the Pool.
|
||||||
func (mp *Pool) Add(t *transaction.Transaction, fee Feer) error {
|
func (mp *Pool) Add(t *transaction.Transaction, fee Feer, data ...interface{}) error {
|
||||||
var pItem = item{
|
var pItem = item{
|
||||||
txn: t,
|
txn: t,
|
||||||
blockStamp: fee.BlockHeight(),
|
blockStamp: fee.BlockHeight(),
|
||||||
}
|
}
|
||||||
|
if data != nil {
|
||||||
|
pItem.data = data[0]
|
||||||
|
}
|
||||||
mp.lock.Lock()
|
mp.lock.Lock()
|
||||||
if mp.containsKey(t.Hash()) {
|
if mp.containsKey(t.Hash()) {
|
||||||
mp.lock.Unlock()
|
mp.lock.Unlock()
|
||||||
|
@ -281,14 +287,16 @@ func (mp *Pool) removeInternal(hash util.Uint256, feer Feer) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
itm := mp.verifiedTxes[num]
|
||||||
if num < len(mp.verifiedTxes)-1 {
|
if num < len(mp.verifiedTxes)-1 {
|
||||||
mp.verifiedTxes = append(mp.verifiedTxes[:num], mp.verifiedTxes[num+1:]...)
|
mp.verifiedTxes = append(mp.verifiedTxes[:num], mp.verifiedTxes[num+1:]...)
|
||||||
} else if num == len(mp.verifiedTxes)-1 {
|
} else if num == len(mp.verifiedTxes)-1 {
|
||||||
mp.verifiedTxes = mp.verifiedTxes[:num]
|
mp.verifiedTxes = mp.verifiedTxes[:num]
|
||||||
}
|
}
|
||||||
senderFee := mp.fees[tx.Sender()]
|
payer := itm.txn.Signers[mp.payerIndex].Account
|
||||||
|
senderFee := mp.fees[payer]
|
||||||
senderFee.feeSum.Sub(senderFee.feeSum, big.NewInt(tx.SystemFee+tx.NetworkFee))
|
senderFee.feeSum.Sub(senderFee.feeSum, big.NewInt(tx.SystemFee+tx.NetworkFee))
|
||||||
mp.fees[tx.Sender()] = senderFee
|
mp.fees[payer] = senderFee
|
||||||
if feer.P2PSigExtensionsEnabled() {
|
if feer.P2PSigExtensionsEnabled() {
|
||||||
// remove all conflicting hashes from mp.conflicts list
|
// remove all conflicting hashes from mp.conflicts list
|
||||||
mp.removeConflictsOf(tx)
|
mp.removeConflictsOf(tx)
|
||||||
|
@ -314,7 +322,7 @@ func (mp *Pool) RemoveStale(isOK func(*transaction.Transaction) bool, feer Feer)
|
||||||
mp.conflicts = make(map[util.Uint256][]util.Uint256)
|
mp.conflicts = make(map[util.Uint256][]util.Uint256)
|
||||||
}
|
}
|
||||||
height := feer.BlockHeight()
|
height := feer.BlockHeight()
|
||||||
var staleTxs []*transaction.Transaction
|
var staleItems []item
|
||||||
for _, itm := range mp.verifiedTxes {
|
for _, itm := range mp.verifiedTxes {
|
||||||
if isOK(itm.txn) && mp.checkPolicy(itm.txn, policyChanged) && mp.tryAddSendersFee(itm.txn, feer, true) {
|
if isOK(itm.txn) && mp.checkPolicy(itm.txn, policyChanged) && mp.tryAddSendersFee(itm.txn, feer, true) {
|
||||||
newVerifiedTxes = append(newVerifiedTxes, itm)
|
newVerifiedTxes = append(newVerifiedTxes, itm)
|
||||||
|
@ -325,11 +333,11 @@ func (mp *Pool) RemoveStale(isOK func(*transaction.Transaction) bool, feer Feer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if mp.resendThreshold != 0 {
|
if mp.resendThreshold != 0 {
|
||||||
// tx is resend at resendThreshold, 2*resendThreshold, 4*resendThreshold ...
|
// item is resend at resendThreshold, 2*resendThreshold, 4*resendThreshold ...
|
||||||
// so quotient must be a power of two.
|
// so quotient must be a power of two.
|
||||||
diff := (height - itm.blockStamp)
|
diff := (height - itm.blockStamp)
|
||||||
if diff%mp.resendThreshold == 0 && bits.OnesCount32(diff/mp.resendThreshold) == 1 {
|
if diff%mp.resendThreshold == 0 && bits.OnesCount32(diff/mp.resendThreshold) == 1 {
|
||||||
staleTxs = append(staleTxs, itm.txn)
|
staleItems = append(staleItems, itm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -339,8 +347,8 @@ func (mp *Pool) RemoveStale(isOK func(*transaction.Transaction) bool, feer Feer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(staleTxs) != 0 {
|
if len(staleItems) != 0 {
|
||||||
go mp.resendStaleTxs(staleTxs)
|
go mp.resendStaleItems(staleItems)
|
||||||
}
|
}
|
||||||
mp.verifiedTxes = newVerifiedTxes
|
mp.verifiedTxes = newVerifiedTxes
|
||||||
mp.lock.Unlock()
|
mp.lock.Unlock()
|
||||||
|
@ -366,11 +374,12 @@ func (mp *Pool) checkPolicy(tx *transaction.Transaction, policyChanged bool) boo
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new Pool struct.
|
// New returns a new Pool struct.
|
||||||
func New(capacity int) *Pool {
|
func New(capacity int, payerIndex int) *Pool {
|
||||||
return &Pool{
|
return &Pool{
|
||||||
verifiedMap: make(map[util.Uint256]*transaction.Transaction),
|
verifiedMap: make(map[util.Uint256]*transaction.Transaction),
|
||||||
verifiedTxes: make([]item, 0, capacity),
|
verifiedTxes: make([]item, 0, capacity),
|
||||||
capacity: capacity,
|
capacity: capacity,
|
||||||
|
payerIndex: payerIndex,
|
||||||
fees: make(map[util.Uint160]utilityBalanceAndFees),
|
fees: make(map[util.Uint160]utilityBalanceAndFees),
|
||||||
conflicts: make(map[util.Uint256][]util.Uint256),
|
conflicts: make(map[util.Uint256][]util.Uint256),
|
||||||
oracleResp: make(map[uint64]util.Uint256),
|
oracleResp: make(map[uint64]util.Uint256),
|
||||||
|
@ -379,16 +388,16 @@ func New(capacity int) *Pool {
|
||||||
|
|
||||||
// SetResendThreshold sets threshold after which transaction will be considered stale
|
// SetResendThreshold sets threshold after which transaction will be considered stale
|
||||||
// and returned for retransmission by `GetStaleTransactions`.
|
// and returned for retransmission by `GetStaleTransactions`.
|
||||||
func (mp *Pool) SetResendThreshold(h uint32, f func(*transaction.Transaction)) {
|
func (mp *Pool) SetResendThreshold(h uint32, f func(*transaction.Transaction, interface{})) {
|
||||||
mp.lock.Lock()
|
mp.lock.Lock()
|
||||||
defer mp.lock.Unlock()
|
defer mp.lock.Unlock()
|
||||||
mp.resendThreshold = h
|
mp.resendThreshold = h
|
||||||
mp.resendFunc = f
|
mp.resendFunc = f
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mp *Pool) resendStaleTxs(txs []*transaction.Transaction) {
|
func (mp *Pool) resendStaleItems(items []item) {
|
||||||
for i := range txs {
|
for i := range items {
|
||||||
mp.resendFunc(txs[i])
|
mp.resendFunc(items[i].txn, items[i].data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -403,6 +412,30 @@ func (mp *Pool) TryGetValue(hash util.Uint256) (*transaction.Transaction, bool)
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TryGetData returns data associated with the specified transaction if it exists in the memory pool.
|
||||||
|
func (mp *Pool) TryGetData(hash util.Uint256) (interface{}, bool) {
|
||||||
|
mp.lock.RLock()
|
||||||
|
defer mp.lock.RUnlock()
|
||||||
|
if tx, ok := mp.verifiedMap[hash]; ok {
|
||||||
|
itm := item{txn: tx}
|
||||||
|
n := sort.Search(len(mp.verifiedTxes), func(n int) bool {
|
||||||
|
return itm.CompareTo(mp.verifiedTxes[n]) >= 0
|
||||||
|
})
|
||||||
|
if n < len(mp.verifiedTxes) {
|
||||||
|
for i := n; i < len(mp.verifiedTxes); i++ { // items may have equal priority, so `n` is the left bound of the items which are as prioritized as the desired `itm`.
|
||||||
|
if mp.verifiedTxes[i].txn.Hash() == hash {
|
||||||
|
return mp.verifiedTxes[i].data, ok
|
||||||
|
}
|
||||||
|
if itm.CompareTo(mp.verifiedTxes[i]) != 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
// GetVerifiedTransactions returns a slice of transactions with their fees.
|
// GetVerifiedTransactions returns a slice of transactions with their fees.
|
||||||
func (mp *Pool) GetVerifiedTransactions() []*transaction.Transaction {
|
func (mp *Pool) GetVerifiedTransactions() []*transaction.Transaction {
|
||||||
mp.lock.RLock()
|
mp.lock.RLock()
|
||||||
|
@ -420,9 +453,10 @@ func (mp *Pool) GetVerifiedTransactions() []*transaction.Transaction {
|
||||||
// checkTxConflicts is an internal unprotected version of Verify. It takes into
|
// checkTxConflicts is an internal unprotected version of Verify. It takes into
|
||||||
// consideration conflicting transactions which are about to be removed from mempool.
|
// consideration conflicting transactions which are about to be removed from mempool.
|
||||||
func (mp *Pool) checkTxConflicts(tx *transaction.Transaction, fee Feer) ([]*transaction.Transaction, error) {
|
func (mp *Pool) checkTxConflicts(tx *transaction.Transaction, fee Feer) ([]*transaction.Transaction, error) {
|
||||||
actualSenderFee, ok := mp.fees[tx.Sender()]
|
payer := tx.Signers[mp.payerIndex].Account
|
||||||
|
actualSenderFee, ok := mp.fees[payer]
|
||||||
if !ok {
|
if !ok {
|
||||||
actualSenderFee.balance = fee.GetUtilityTokenBalance(tx.Sender())
|
actualSenderFee.balance = fee.GetUtilityTokenBalance(payer)
|
||||||
actualSenderFee.feeSum = big.NewInt(0)
|
actualSenderFee.feeSum = big.NewInt(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -434,7 +468,7 @@ func (mp *Pool) checkTxConflicts(tx *transaction.Transaction, fee Feer) ([]*tran
|
||||||
if conflictingHashes, ok := mp.conflicts[tx.Hash()]; ok {
|
if conflictingHashes, ok := mp.conflicts[tx.Hash()]; ok {
|
||||||
for _, hash := range conflictingHashes {
|
for _, hash := range conflictingHashes {
|
||||||
existingTx := mp.verifiedMap[hash]
|
existingTx := mp.verifiedMap[hash]
|
||||||
if existingTx.HasSigner(tx.Sender()) && existingTx.NetworkFee > tx.NetworkFee {
|
if existingTx.HasSigner(payer) && existingTx.NetworkFee > tx.NetworkFee {
|
||||||
return nil, fmt.Errorf("%w: conflicting transaction %s has bigger network fee", ErrConflictsAttribute, existingTx.Hash().StringBE())
|
return nil, fmt.Errorf("%w: conflicting transaction %s has bigger network fee", ErrConflictsAttribute, existingTx.Hash().StringBE())
|
||||||
}
|
}
|
||||||
conflictsToBeRemoved = append(conflictsToBeRemoved, existingTx)
|
conflictsToBeRemoved = append(conflictsToBeRemoved, existingTx)
|
||||||
|
@ -447,7 +481,7 @@ func (mp *Pool) checkTxConflicts(tx *transaction.Transaction, fee Feer) ([]*tran
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !tx.HasSigner(existingTx.Sender()) {
|
if !tx.HasSigner(existingTx.Signers[mp.payerIndex].Account) {
|
||||||
return nil, fmt.Errorf("%w: not signed by the sender of conflicting transaction %s", ErrConflictsAttribute, existingTx.Hash().StringBE())
|
return nil, fmt.Errorf("%w: not signed by the sender of conflicting transaction %s", ErrConflictsAttribute, existingTx.Hash().StringBE())
|
||||||
}
|
}
|
||||||
if existingTx.NetworkFee >= tx.NetworkFee {
|
if existingTx.NetworkFee >= tx.NetworkFee {
|
||||||
|
@ -461,7 +495,7 @@ func (mp *Pool) checkTxConflicts(tx *transaction.Transaction, fee Feer) ([]*tran
|
||||||
feeSum: new(big.Int).Set(actualSenderFee.feeSum),
|
feeSum: new(big.Int).Set(actualSenderFee.feeSum),
|
||||||
}
|
}
|
||||||
for _, conflictingTx := range conflictsToBeRemoved {
|
for _, conflictingTx := range conflictsToBeRemoved {
|
||||||
if conflictingTx.Sender().Equals(tx.Sender()) {
|
if conflictingTx.Signers[mp.payerIndex].Account.Equals(payer) {
|
||||||
expectedSenderFee.feeSum.Sub(expectedSenderFee.feeSum, big.NewInt(conflictingTx.SystemFee+conflictingTx.NetworkFee))
|
expectedSenderFee.feeSum.Sub(expectedSenderFee.feeSum, big.NewInt(conflictingTx.SystemFee+conflictingTx.NetworkFee))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/internal/random"
|
"github.com/nspcc-dev/neo-go/internal/random"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -20,10 +21,9 @@ type FeerStub struct {
|
||||||
feePerByte int64
|
feePerByte int64
|
||||||
p2pSigExt bool
|
p2pSigExt bool
|
||||||
blockHeight uint32
|
blockHeight uint32
|
||||||
|
balance int64
|
||||||
}
|
}
|
||||||
|
|
||||||
var balance = big.NewInt(10000000)
|
|
||||||
|
|
||||||
func (fs *FeerStub) FeePerByte() int64 {
|
func (fs *FeerStub) FeePerByte() int64 {
|
||||||
return fs.feePerByte
|
return fs.feePerByte
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ func (fs *FeerStub) BlockHeight() uint32 {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FeerStub) GetUtilityTokenBalance(uint160 util.Uint160) *big.Int {
|
func (fs *FeerStub) GetUtilityTokenBalance(uint160 util.Uint160) *big.Int {
|
||||||
return balance
|
return big.NewInt(fs.balance)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FeerStub) P2PSigExtensionsEnabled() bool {
|
func (fs *FeerStub) P2PSigExtensionsEnabled() bool {
|
||||||
|
@ -41,7 +41,7 @@ func (fs *FeerStub) P2PSigExtensionsEnabled() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMemPoolAddRemoveWithFeer(t *testing.T, fs Feer) {
|
func testMemPoolAddRemoveWithFeer(t *testing.T, fs Feer) {
|
||||||
mp := New(10)
|
mp := New(10, 0)
|
||||||
tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
||||||
tx.Nonce = 0
|
tx.Nonce = 0
|
||||||
tx.Signers = []transaction.Signer{{Account: util.Uint160{1, 2, 3}}}
|
tx.Signers = []transaction.Signer{{Account: util.Uint160{1, 2, 3}}}
|
||||||
|
@ -62,7 +62,7 @@ func testMemPoolAddRemoveWithFeer(t *testing.T, fs Feer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMemPoolRemoveStale(t *testing.T) {
|
func TestMemPoolRemoveStale(t *testing.T) {
|
||||||
mp := New(5)
|
mp := New(5, 0)
|
||||||
txs := make([]*transaction.Transaction, 5)
|
txs := make([]*transaction.Transaction, 5)
|
||||||
for i := range txs {
|
for i := range txs {
|
||||||
txs[i] = transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
txs[i] = transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
||||||
|
@ -72,7 +72,7 @@ func TestMemPoolRemoveStale(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
staleTxs := make(chan *transaction.Transaction, 5)
|
staleTxs := make(chan *transaction.Transaction, 5)
|
||||||
f := func(tx *transaction.Transaction) {
|
f := func(tx *transaction.Transaction, _ interface{}) {
|
||||||
staleTxs <- tx
|
staleTxs <- tx
|
||||||
}
|
}
|
||||||
mp.SetResendThreshold(5, f)
|
mp.SetResendThreshold(5, f)
|
||||||
|
@ -111,9 +111,9 @@ func TestMemPoolAddRemove(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOverCapacity(t *testing.T) {
|
func TestOverCapacity(t *testing.T) {
|
||||||
var fs = &FeerStub{}
|
var fs = &FeerStub{balance: 10000000}
|
||||||
const mempoolSize = 10
|
const mempoolSize = 10
|
||||||
mp := New(mempoolSize)
|
mp := New(mempoolSize, 0)
|
||||||
|
|
||||||
for i := 0; i < mempoolSize; i++ {
|
for i := 0; i < mempoolSize; i++ {
|
||||||
tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
||||||
|
@ -186,7 +186,7 @@ func TestOverCapacity(t *testing.T) {
|
||||||
func TestGetVerified(t *testing.T) {
|
func TestGetVerified(t *testing.T) {
|
||||||
var fs = &FeerStub{}
|
var fs = &FeerStub{}
|
||||||
const mempoolSize = 10
|
const mempoolSize = 10
|
||||||
mp := New(mempoolSize)
|
mp := New(mempoolSize, 0)
|
||||||
|
|
||||||
txes := make([]*transaction.Transaction, 0, mempoolSize)
|
txes := make([]*transaction.Transaction, 0, mempoolSize)
|
||||||
for i := 0; i < mempoolSize; i++ {
|
for i := 0; i < mempoolSize; i++ {
|
||||||
|
@ -210,7 +210,7 @@ func TestGetVerified(t *testing.T) {
|
||||||
func TestRemoveStale(t *testing.T) {
|
func TestRemoveStale(t *testing.T) {
|
||||||
var fs = &FeerStub{}
|
var fs = &FeerStub{}
|
||||||
const mempoolSize = 10
|
const mempoolSize = 10
|
||||||
mp := New(mempoolSize)
|
mp := New(mempoolSize, 0)
|
||||||
|
|
||||||
txes1 := make([]*transaction.Transaction, 0, mempoolSize/2)
|
txes1 := make([]*transaction.Transaction, 0, mempoolSize/2)
|
||||||
txes2 := make([]*transaction.Transaction, 0, mempoolSize/2)
|
txes2 := make([]*transaction.Transaction, 0, mempoolSize/2)
|
||||||
|
@ -243,50 +243,51 @@ func TestRemoveStale(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMemPoolFees(t *testing.T) {
|
func TestMemPoolFees(t *testing.T) {
|
||||||
mp := New(10)
|
mp := New(10, 0)
|
||||||
|
fs := &FeerStub{balance: 10000000}
|
||||||
sender0 := util.Uint160{1, 2, 3}
|
sender0 := util.Uint160{1, 2, 3}
|
||||||
tx0 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
tx0 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
||||||
tx0.NetworkFee = balance.Int64() + 1
|
tx0.NetworkFee = fs.balance + 1
|
||||||
tx0.Signers = []transaction.Signer{{Account: sender0}}
|
tx0.Signers = []transaction.Signer{{Account: sender0}}
|
||||||
// insufficient funds to add transaction, and balance shouldn't be stored
|
// insufficient funds to add transaction, and balance shouldn't be stored
|
||||||
require.Equal(t, false, mp.Verify(tx0, &FeerStub{}))
|
require.Equal(t, false, mp.Verify(tx0, fs))
|
||||||
require.Error(t, mp.Add(tx0, &FeerStub{}))
|
require.Error(t, mp.Add(tx0, fs))
|
||||||
require.Equal(t, 0, len(mp.fees))
|
require.Equal(t, 0, len(mp.fees))
|
||||||
|
|
||||||
balancePart := new(big.Int).Div(balance, big.NewInt(4))
|
balancePart := new(big.Int).Div(big.NewInt(fs.balance), big.NewInt(4))
|
||||||
// no problems with adding another transaction with lower fee
|
// no problems with adding another transaction with lower fee
|
||||||
tx1 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
tx1 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
||||||
tx1.NetworkFee = balancePart.Int64()
|
tx1.NetworkFee = balancePart.Int64()
|
||||||
tx1.Signers = []transaction.Signer{{Account: sender0}}
|
tx1.Signers = []transaction.Signer{{Account: sender0}}
|
||||||
require.NoError(t, mp.Add(tx1, &FeerStub{}))
|
require.NoError(t, mp.Add(tx1, fs))
|
||||||
require.Equal(t, 1, len(mp.fees))
|
require.Equal(t, 1, len(mp.fees))
|
||||||
require.Equal(t, utilityBalanceAndFees{
|
require.Equal(t, utilityBalanceAndFees{
|
||||||
balance: balance,
|
balance: big.NewInt(fs.balance),
|
||||||
feeSum: big.NewInt(tx1.NetworkFee),
|
feeSum: big.NewInt(tx1.NetworkFee),
|
||||||
}, mp.fees[sender0])
|
}, mp.fees[sender0])
|
||||||
|
|
||||||
// balance shouldn't change after adding one more transaction
|
// balance shouldn't change after adding one more transaction
|
||||||
tx2 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
tx2 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
||||||
tx2.NetworkFee = new(big.Int).Sub(balance, balancePart).Int64()
|
tx2.NetworkFee = new(big.Int).Sub(big.NewInt(fs.balance), balancePart).Int64()
|
||||||
tx2.Signers = []transaction.Signer{{Account: sender0}}
|
tx2.Signers = []transaction.Signer{{Account: sender0}}
|
||||||
require.NoError(t, mp.Add(tx2, &FeerStub{}))
|
require.NoError(t, mp.Add(tx2, fs))
|
||||||
require.Equal(t, 2, len(mp.verifiedTxes))
|
require.Equal(t, 2, len(mp.verifiedTxes))
|
||||||
require.Equal(t, 1, len(mp.fees))
|
require.Equal(t, 1, len(mp.fees))
|
||||||
require.Equal(t, utilityBalanceAndFees{
|
require.Equal(t, utilityBalanceAndFees{
|
||||||
balance: balance,
|
balance: big.NewInt(fs.balance),
|
||||||
feeSum: balance,
|
feeSum: big.NewInt(fs.balance),
|
||||||
}, mp.fees[sender0])
|
}, mp.fees[sender0])
|
||||||
|
|
||||||
// can't add more transactions as we don't have enough GAS
|
// can't add more transactions as we don't have enough GAS
|
||||||
tx3 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
tx3 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
||||||
tx3.NetworkFee = 1
|
tx3.NetworkFee = 1
|
||||||
tx3.Signers = []transaction.Signer{{Account: sender0}}
|
tx3.Signers = []transaction.Signer{{Account: sender0}}
|
||||||
require.Equal(t, false, mp.Verify(tx3, &FeerStub{}))
|
require.Equal(t, false, mp.Verify(tx3, fs))
|
||||||
require.Error(t, mp.Add(tx3, &FeerStub{}))
|
require.Error(t, mp.Add(tx3, fs))
|
||||||
require.Equal(t, 1, len(mp.fees))
|
require.Equal(t, 1, len(mp.fees))
|
||||||
require.Equal(t, utilityBalanceAndFees{
|
require.Equal(t, utilityBalanceAndFees{
|
||||||
balance: balance,
|
balance: big.NewInt(fs.balance),
|
||||||
feeSum: balance,
|
feeSum: big.NewInt(fs.balance),
|
||||||
}, mp.fees[sender0])
|
}, mp.fees[sender0])
|
||||||
|
|
||||||
// check whether sender's fee updates correctly
|
// check whether sender's fee updates correctly
|
||||||
|
@ -295,10 +296,10 @@ func TestMemPoolFees(t *testing.T) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}, &FeerStub{})
|
}, fs)
|
||||||
require.Equal(t, 1, len(mp.fees))
|
require.Equal(t, 1, len(mp.fees))
|
||||||
require.Equal(t, utilityBalanceAndFees{
|
require.Equal(t, utilityBalanceAndFees{
|
||||||
balance: balance,
|
balance: big.NewInt(fs.balance),
|
||||||
feeSum: big.NewInt(tx2.NetworkFee),
|
feeSum: big.NewInt(tx2.NetworkFee),
|
||||||
}, mp.fees[sender0])
|
}, mp.fees[sender0])
|
||||||
|
|
||||||
|
@ -308,12 +309,13 @@ func TestMemPoolFees(t *testing.T) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}, &FeerStub{})
|
}, fs)
|
||||||
require.Equal(t, 0, len(mp.fees))
|
require.Equal(t, 0, len(mp.fees))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMempoolItemsOrder(t *testing.T) {
|
func TestMempoolItemsOrder(t *testing.T) {
|
||||||
sender0 := util.Uint160{1, 2, 3}
|
sender0 := util.Uint160{1, 2, 3}
|
||||||
|
balance := big.NewInt(10000000)
|
||||||
|
|
||||||
tx1 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
tx1 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
||||||
tx1.NetworkFee = new(big.Int).Div(balance, big.NewInt(8)).Int64()
|
tx1.NetworkFee = new(big.Int).Div(balance, big.NewInt(8)).Int64()
|
||||||
|
@ -352,9 +354,9 @@ func TestMempoolItemsOrder(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMempoolAddRemoveOracleResponse(t *testing.T) {
|
func TestMempoolAddRemoveOracleResponse(t *testing.T) {
|
||||||
mp := New(5)
|
mp := New(5, 0)
|
||||||
nonce := uint32(0)
|
nonce := uint32(0)
|
||||||
fs := &FeerStub{}
|
fs := &FeerStub{balance: 10000}
|
||||||
newTx := func(netFee int64, id uint64) *transaction.Transaction {
|
newTx := func(netFee int64, id uint64) *transaction.Transaction {
|
||||||
tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
||||||
tx.NetworkFee = netFee
|
tx.NetworkFee = netFee
|
||||||
|
@ -406,9 +408,9 @@ func TestMempoolAddRemoveOracleResponse(t *testing.T) {
|
||||||
|
|
||||||
func TestMempoolAddRemoveConflicts(t *testing.T) {
|
func TestMempoolAddRemoveConflicts(t *testing.T) {
|
||||||
capacity := 6
|
capacity := 6
|
||||||
mp := New(capacity)
|
mp := New(capacity, 0)
|
||||||
var (
|
var (
|
||||||
fs = &FeerStub{p2pSigExt: true}
|
fs = &FeerStub{p2pSigExt: true, balance: 100000}
|
||||||
nonce uint32 = 1
|
nonce uint32 = 1
|
||||||
)
|
)
|
||||||
getConflictsTx := func(netFee int64, hashes ...util.Uint256) *transaction.Transaction {
|
getConflictsTx := func(netFee int64, hashes ...util.Uint256) *transaction.Transaction {
|
||||||
|
@ -524,3 +526,116 @@ func TestMempoolAddRemoveConflicts(t *testing.T) {
|
||||||
require.Equal(t, false, ok)
|
require.Equal(t, false, ok)
|
||||||
require.True(t, errors.Is(mp.Add(tx13, fs), ErrConflictsAttribute))
|
require.True(t, errors.Is(mp.Add(tx13, fs), ErrConflictsAttribute))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMempoolAddWithDataGetData(t *testing.T) {
|
||||||
|
var (
|
||||||
|
smallNetFee int64 = 3
|
||||||
|
nonce uint32
|
||||||
|
)
|
||||||
|
fs := &FeerStub{
|
||||||
|
feePerByte: 0,
|
||||||
|
p2pSigExt: true,
|
||||||
|
blockHeight: 5,
|
||||||
|
balance: 100,
|
||||||
|
}
|
||||||
|
mp := New(10, 1)
|
||||||
|
newTx := func(t *testing.T, netFee int64) *transaction.Transaction {
|
||||||
|
tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.RET)}, 0)
|
||||||
|
tx.Signers = []transaction.Signer{{}, {}}
|
||||||
|
tx.NetworkFee = netFee
|
||||||
|
nonce++
|
||||||
|
tx.Nonce = nonce
|
||||||
|
return tx
|
||||||
|
}
|
||||||
|
|
||||||
|
// bad, insufficient deposit
|
||||||
|
r1 := &payload.P2PNotaryRequest{
|
||||||
|
MainTransaction: newTx(t, 0),
|
||||||
|
FallbackTransaction: newTx(t, fs.balance+1),
|
||||||
|
}
|
||||||
|
require.True(t, errors.Is(mp.Add(r1.FallbackTransaction, fs, r1), ErrInsufficientFunds))
|
||||||
|
|
||||||
|
// good
|
||||||
|
r2 := &payload.P2PNotaryRequest{
|
||||||
|
MainTransaction: newTx(t, 0),
|
||||||
|
FallbackTransaction: newTx(t, smallNetFee),
|
||||||
|
}
|
||||||
|
require.NoError(t, mp.Add(r2.FallbackTransaction, fs, r2))
|
||||||
|
require.True(t, mp.ContainsKey(r2.FallbackTransaction.Hash()))
|
||||||
|
data, ok := mp.TryGetData(r2.FallbackTransaction.Hash())
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, r2, data)
|
||||||
|
|
||||||
|
// bad, already in pool
|
||||||
|
require.True(t, errors.Is(mp.Add(r2.FallbackTransaction, fs, r2), ErrDup))
|
||||||
|
|
||||||
|
// good, higher priority than r2. The resulting mp.verifiedTxes: [r3, r2]
|
||||||
|
r3 := &payload.P2PNotaryRequest{
|
||||||
|
MainTransaction: newTx(t, 0),
|
||||||
|
FallbackTransaction: newTx(t, smallNetFee+1),
|
||||||
|
}
|
||||||
|
require.NoError(t, mp.Add(r3.FallbackTransaction, fs, r3))
|
||||||
|
require.True(t, mp.ContainsKey(r3.FallbackTransaction.Hash()))
|
||||||
|
data, ok = mp.TryGetData(r3.FallbackTransaction.Hash())
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, r3, data)
|
||||||
|
|
||||||
|
// good, same priority as r2. The resulting mp.verifiedTxes: [r3, r2, r4]
|
||||||
|
r4 := &payload.P2PNotaryRequest{
|
||||||
|
MainTransaction: newTx(t, 0),
|
||||||
|
FallbackTransaction: newTx(t, smallNetFee),
|
||||||
|
}
|
||||||
|
require.NoError(t, mp.Add(r4.FallbackTransaction, fs, r4))
|
||||||
|
require.True(t, mp.ContainsKey(r4.FallbackTransaction.Hash()))
|
||||||
|
data, ok = mp.TryGetData(r4.FallbackTransaction.Hash())
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, r4, data)
|
||||||
|
|
||||||
|
// good, same priority as r2. The resulting mp.verifiedTxes: [r3, r2, r4, r5]
|
||||||
|
r5 := &payload.P2PNotaryRequest{
|
||||||
|
MainTransaction: newTx(t, 0),
|
||||||
|
FallbackTransaction: newTx(t, smallNetFee),
|
||||||
|
}
|
||||||
|
require.NoError(t, mp.Add(r5.FallbackTransaction, fs, r5))
|
||||||
|
require.True(t, mp.ContainsKey(r5.FallbackTransaction.Hash()))
|
||||||
|
data, ok = mp.TryGetData(r5.FallbackTransaction.Hash())
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, r5, data)
|
||||||
|
|
||||||
|
// and both r2's and r4's data should still be reachable
|
||||||
|
data, ok = mp.TryGetData(r2.FallbackTransaction.Hash())
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, r2, data)
|
||||||
|
data, ok = mp.TryGetData(r4.FallbackTransaction.Hash())
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, r4, data)
|
||||||
|
|
||||||
|
// should fail to get unexisting data
|
||||||
|
_, ok = mp.TryGetData(util.Uint256{0, 0, 0})
|
||||||
|
require.False(t, ok)
|
||||||
|
|
||||||
|
// but getting nil data is OK. The resulting mp.verifiedTxes: [r3, r2, r4, r5, r6]
|
||||||
|
r6 := newTx(t, smallNetFee)
|
||||||
|
require.NoError(t, mp.Add(r6, fs, nil))
|
||||||
|
require.True(t, mp.ContainsKey(r6.Hash()))
|
||||||
|
data, ok = mp.TryGetData(r6.Hash())
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Nil(t, data)
|
||||||
|
|
||||||
|
// getting data: item is in verifiedMap, but not in verifiedTxes
|
||||||
|
r7 := &payload.P2PNotaryRequest{
|
||||||
|
MainTransaction: newTx(t, 0),
|
||||||
|
FallbackTransaction: newTx(t, smallNetFee),
|
||||||
|
}
|
||||||
|
require.NoError(t, mp.Add(r7.FallbackTransaction, fs, r4))
|
||||||
|
require.True(t, mp.ContainsKey(r7.FallbackTransaction.Hash()))
|
||||||
|
r8 := &payload.P2PNotaryRequest{
|
||||||
|
MainTransaction: newTx(t, 0),
|
||||||
|
FallbackTransaction: newTx(t, smallNetFee-1),
|
||||||
|
}
|
||||||
|
require.NoError(t, mp.Add(r8.FallbackTransaction, fs, r4))
|
||||||
|
require.True(t, mp.ContainsKey(r8.FallbackTransaction.Hash()))
|
||||||
|
mp.verifiedTxes = append(mp.verifiedTxes[:len(mp.verifiedTxes)-2], mp.verifiedTxes[len(mp.verifiedTxes)-1])
|
||||||
|
_, ok = mp.TryGetData(r7.FallbackTransaction.Hash())
|
||||||
|
require.False(t, ok)
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||||
|
@ -26,17 +27,29 @@ type Notary struct {
|
||||||
interop.ContractMD
|
interop.ContractMD
|
||||||
GAS *GAS
|
GAS *GAS
|
||||||
Desig *Designate
|
Desig *Designate
|
||||||
|
|
||||||
|
lock sync.RWMutex
|
||||||
|
// isValid defies whether cached values were changed during the current
|
||||||
|
// consensus iteration. If false, these values will be updated after
|
||||||
|
// blockchain DAO persisting. If true, we can safely use cached values.
|
||||||
|
isValid bool
|
||||||
|
maxNotValidBeforeDelta uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
notaryName = "Notary"
|
notaryName = "Notary"
|
||||||
notaryContractID = reservedContractID - 1
|
notaryContractID = reservedContractID - 1
|
||||||
|
// NotaryVerificationPrice is the price of `verify` Notary method.
|
||||||
|
NotaryVerificationPrice = 100_0000
|
||||||
|
|
||||||
// prefixDeposit is a prefix for storing Notary deposits.
|
// prefixDeposit is a prefix for storing Notary deposits.
|
||||||
prefixDeposit = 1
|
prefixDeposit = 1
|
||||||
defaultDepositDeltaTill = 5760
|
defaultDepositDeltaTill = 5760
|
||||||
|
defaultMaxNotValidBeforeDelta = 140 // 20 rounds for 7 validators, a little more than half an hour
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var maxNotValidBeforeDeltaKey = []byte{10}
|
||||||
|
|
||||||
// newNotary returns Notary native contract.
|
// newNotary returns Notary native contract.
|
||||||
func newNotary() *Notary {
|
func newNotary() *Notary {
|
||||||
n := &Notary{ContractMD: *interop.NewContractMD(notaryName)}
|
n := &Notary{ContractMD: *interop.NewContractMD(notaryName)}
|
||||||
|
@ -76,6 +89,15 @@ func newNotary() *Notary {
|
||||||
md = newMethodAndPrice(n.verify, 100_0000, smartcontract.AllowStates)
|
md = newMethodAndPrice(n.verify, 100_0000, smartcontract.AllowStates)
|
||||||
n.AddMethod(md, desc)
|
n.AddMethod(md, desc)
|
||||||
|
|
||||||
|
desc = newDescriptor("getMaxNotValidBeforeDelta", smartcontract.IntegerType)
|
||||||
|
md = newMethodAndPrice(n.getMaxNotValidBeforeDelta, 100_0000, smartcontract.AllowStates)
|
||||||
|
n.AddMethod(md, desc)
|
||||||
|
|
||||||
|
desc = newDescriptor("setMaxNotValidBeforeDelta", smartcontract.BoolType,
|
||||||
|
manifest.NewParameter("value", smartcontract.IntegerType))
|
||||||
|
md = newMethodAndPrice(n.setMaxNotValidBeforeDelta, 300_0000, smartcontract.AllowModifyStates)
|
||||||
|
n.AddMethod(md, desc)
|
||||||
|
|
||||||
desc = newDescriptor("onPersist", smartcontract.VoidType)
|
desc = newDescriptor("onPersist", smartcontract.VoidType)
|
||||||
md = newMethodAndPrice(getOnPersistWrapper(n.OnPersist), 0, smartcontract.AllowModifyStates)
|
md = newMethodAndPrice(getOnPersistWrapper(n.OnPersist), 0, smartcontract.AllowModifyStates)
|
||||||
n.AddMethod(md, desc)
|
n.AddMethod(md, desc)
|
||||||
|
@ -94,6 +116,8 @@ func (n *Notary) Metadata() *interop.ContractMD {
|
||||||
|
|
||||||
// Initialize initializes Notary native contract and implements Contract interface.
|
// Initialize initializes Notary native contract and implements Contract interface.
|
||||||
func (n *Notary) Initialize(ic *interop.Context) error {
|
func (n *Notary) Initialize(ic *interop.Context) error {
|
||||||
|
n.isValid = true
|
||||||
|
n.maxNotValidBeforeDelta = defaultMaxNotValidBeforeDelta
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +140,7 @@ func (n *Notary) OnPersist(ic *interop.Context) error {
|
||||||
nFees += int64(nKeys) + 1
|
nFees += int64(nKeys) + 1
|
||||||
if tx.Sender() == n.Hash {
|
if tx.Sender() == n.Hash {
|
||||||
payer := tx.Signers[1]
|
payer := tx.Signers[1]
|
||||||
balance := n.getDepositFor(ic.DAO, payer.Account)
|
balance := n.GetDepositFor(ic.DAO, payer.Account)
|
||||||
balance.Amount.Sub(balance.Amount, big.NewInt(tx.SystemFee+tx.NetworkFee))
|
balance.Amount.Sub(balance.Amount, big.NewInt(tx.SystemFee+tx.NetworkFee))
|
||||||
if balance.Amount.Sign() == 0 {
|
if balance.Amount.Sign() == 0 {
|
||||||
err := n.removeDepositFor(ic.DAO, payer.Account)
|
err := n.removeDepositFor(ic.DAO, payer.Account)
|
||||||
|
@ -142,6 +166,19 @@ func (n *Notary) OnPersist(ic *interop.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnPersistEnd updates cached Policy values if they've been changed
|
||||||
|
func (n *Notary) OnPersistEnd(dao dao.DAO) error {
|
||||||
|
if n.isValid {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
n.lock.Lock()
|
||||||
|
defer n.lock.Unlock()
|
||||||
|
|
||||||
|
n.maxNotValidBeforeDelta = getUint32WithKey(n.ContractID, dao, maxNotValidBeforeDeltaKey, defaultMaxNotValidBeforeDelta)
|
||||||
|
n.isValid = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// onPayment records deposited amount as belonging to "from" address with a lock
|
// onPayment records deposited amount as belonging to "from" address with a lock
|
||||||
// till the specified chain's height.
|
// till the specified chain's height.
|
||||||
func (n *Notary) onPayment(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
func (n *Notary) onPayment(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
|
@ -162,7 +199,7 @@ func (n *Notary) onPayment(ic *interop.Context, args []stackitem.Item) stackitem
|
||||||
|
|
||||||
allowedChangeTill := ic.Tx.Sender() == to
|
allowedChangeTill := ic.Tx.Sender() == to
|
||||||
currentHeight := ic.Chain.BlockHeight()
|
currentHeight := ic.Chain.BlockHeight()
|
||||||
deposit := n.getDepositFor(ic.DAO, to)
|
deposit := n.GetDepositFor(ic.DAO, to)
|
||||||
till := toUint32(additionalParams[1])
|
till := toUint32(additionalParams[1])
|
||||||
if till < currentHeight {
|
if till < currentHeight {
|
||||||
panic(fmt.Errorf("`till` shouldn't be less then the chain's height %d", currentHeight))
|
panic(fmt.Errorf("`till` shouldn't be less then the chain's height %d", currentHeight))
|
||||||
|
@ -206,7 +243,7 @@ func (n *Notary) lockDepositUntil(ic *interop.Context, args []stackitem.Item) st
|
||||||
if till < ic.Chain.BlockHeight() {
|
if till < ic.Chain.BlockHeight() {
|
||||||
return stackitem.NewBool(false)
|
return stackitem.NewBool(false)
|
||||||
}
|
}
|
||||||
deposit := n.getDepositFor(ic.DAO, addr)
|
deposit := n.GetDepositFor(ic.DAO, addr)
|
||||||
if deposit == nil {
|
if deposit == nil {
|
||||||
return stackitem.NewBool(false)
|
return stackitem.NewBool(false)
|
||||||
}
|
}
|
||||||
|
@ -235,7 +272,7 @@ func (n *Notary) withdraw(ic *interop.Context, args []stackitem.Item) stackitem.
|
||||||
if !args[1].Equals(stackitem.Null{}) {
|
if !args[1].Equals(stackitem.Null{}) {
|
||||||
to = toUint160(args[1])
|
to = toUint160(args[1])
|
||||||
}
|
}
|
||||||
deposit := n.getDepositFor(ic.DAO, from)
|
deposit := n.GetDepositFor(ic.DAO, from)
|
||||||
if deposit == nil {
|
if deposit == nil {
|
||||||
return stackitem.NewBool(false)
|
return stackitem.NewBool(false)
|
||||||
}
|
}
|
||||||
|
@ -263,21 +300,31 @@ func (n *Notary) withdraw(ic *interop.Context, args []stackitem.Item) stackitem.
|
||||||
// balanceOf returns deposited GAS amount for specified address.
|
// balanceOf returns deposited GAS amount for specified address.
|
||||||
func (n *Notary) balanceOf(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
func (n *Notary) balanceOf(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
acc := toUint160(args[0])
|
acc := toUint160(args[0])
|
||||||
deposit := n.getDepositFor(ic.DAO, acc)
|
return stackitem.NewBigInteger(n.BalanceOf(ic.DAO, acc))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BalanceOf is an internal representation of `balanceOf` Notary method.
|
||||||
|
func (n *Notary) BalanceOf(dao dao.DAO, acc util.Uint160) *big.Int {
|
||||||
|
deposit := n.GetDepositFor(dao, acc)
|
||||||
if deposit == nil {
|
if deposit == nil {
|
||||||
return stackitem.NewBigInteger(big.NewInt(0))
|
return big.NewInt(0)
|
||||||
}
|
}
|
||||||
return stackitem.NewBigInteger(deposit.Amount)
|
return deposit.Amount
|
||||||
}
|
}
|
||||||
|
|
||||||
// expirationOf Returns deposit lock height for specified address.
|
// expirationOf Returns deposit lock height for specified address.
|
||||||
func (n *Notary) expirationOf(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
func (n *Notary) expirationOf(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
acc := toUint160(args[0])
|
acc := toUint160(args[0])
|
||||||
deposit := n.getDepositFor(ic.DAO, acc)
|
return stackitem.Make(n.ExpirationOf(ic.DAO, acc))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpirationOf is an internal representation of `expirationOf` Notary method.
|
||||||
|
func (n *Notary) ExpirationOf(dao dao.DAO, acc util.Uint160) uint32 {
|
||||||
|
deposit := n.GetDepositFor(dao, acc)
|
||||||
if deposit == nil {
|
if deposit == nil {
|
||||||
return stackitem.Make(0)
|
return 0
|
||||||
}
|
}
|
||||||
return stackitem.Make(deposit.Till)
|
return deposit.Till
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify checks whether the transaction was signed by one of the notaries.
|
// verify checks whether the transaction was signed by one of the notaries.
|
||||||
|
@ -302,7 +349,7 @@ func (n *Notary) verify(ic *interop.Context, args []stackitem.Item) stackitem.It
|
||||||
return stackitem.NewBool(false)
|
return stackitem.NewBool(false)
|
||||||
}
|
}
|
||||||
payer := tx.Signers[1].Account
|
payer := tx.Signers[1].Account
|
||||||
balance := n.getDepositFor(ic.DAO, payer)
|
balance := n.GetDepositFor(ic.DAO, payer)
|
||||||
if balance == nil || balance.Amount.Cmp(big.NewInt(tx.NetworkFee+tx.SystemFee)) < 0 {
|
if balance == nil || balance.Amount.Cmp(big.NewInt(tx.NetworkFee+tx.SystemFee)) < 0 {
|
||||||
return stackitem.NewBool(false)
|
return stackitem.NewBool(false)
|
||||||
}
|
}
|
||||||
|
@ -328,9 +375,47 @@ func (n *Notary) GetNotaryNodes(d dao.DAO) (keys.PublicKeys, error) {
|
||||||
return nodes, err
|
return nodes, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// getDepositFor returns state.Deposit for the account specified. It returns nil in case if
|
// getMaxNotValidBeforeDelta is Notary contract method and returns the maximum NotValidBefore delta.
|
||||||
|
func (n *Notary) getMaxNotValidBeforeDelta(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||||
|
return stackitem.NewBigInteger(big.NewInt(int64(n.GetMaxNotValidBeforeDelta(ic.DAO))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMaxNotValidBeforeDelta is an internal representation of Notary getMaxNotValidBeforeDelta method.
|
||||||
|
func (n *Notary) GetMaxNotValidBeforeDelta(dao dao.DAO) uint32 {
|
||||||
|
n.lock.RLock()
|
||||||
|
defer n.lock.RUnlock()
|
||||||
|
if n.isValid {
|
||||||
|
return n.maxNotValidBeforeDelta
|
||||||
|
}
|
||||||
|
return getUint32WithKey(n.ContractID, dao, maxNotValidBeforeDeltaKey, defaultMaxNotValidBeforeDelta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setMaxNotValidBeforeDelta is Notary contract method and sets the maximum NotValidBefore delta.
|
||||||
|
func (n *Notary) setMaxNotValidBeforeDelta(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
|
value := toUint32(args[0])
|
||||||
|
if value > transaction.MaxValidUntilBlockIncrement/2 || value < uint32(ic.Chain.GetConfig().ValidatorsCount) {
|
||||||
|
panic(fmt.Errorf("MaxNotValidBeforeDelta cannot be more than %d or less than %d", transaction.MaxValidUntilBlockIncrement/2, ic.Chain.GetConfig().ValidatorsCount))
|
||||||
|
}
|
||||||
|
ok, err := checkValidators(ic)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("failed to check committee signature: %w", err))
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return stackitem.NewBool(false)
|
||||||
|
}
|
||||||
|
n.lock.Lock()
|
||||||
|
defer n.lock.Unlock()
|
||||||
|
err = setUint32WithKey(n.ContractID, ic.DAO, maxNotValidBeforeDeltaKey, value)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("failed to put value into the storage: %w", err))
|
||||||
|
}
|
||||||
|
n.isValid = false
|
||||||
|
return stackitem.NewBool(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDepositFor returns state.Deposit for the account specified. It returns nil in case if
|
||||||
// deposit is not found in storage and panics in case of any other error.
|
// deposit is not found in storage and panics in case of any other error.
|
||||||
func (n *Notary) getDepositFor(dao dao.DAO, acc util.Uint160) *state.Deposit {
|
func (n *Notary) GetDepositFor(dao dao.DAO, acc util.Uint160) *state.Deposit {
|
||||||
key := append([]byte{prefixDeposit}, acc.BytesBE()...)
|
key := append([]byte{prefixDeposit}, acc.BytesBE()...)
|
||||||
deposit := new(state.Deposit)
|
deposit := new(state.Deposit)
|
||||||
err := getSerializableFromDAO(n.ContractID, dao, key, deposit)
|
err := getSerializableFromDAO(n.ContractID, dao, key, deposit)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package native
|
package native
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -10,7 +9,6 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
||||||
|
@ -167,10 +165,10 @@ func (p *Policy) OnPersistEnd(dao dao.DAO) error {
|
||||||
p.lock.Lock()
|
p.lock.Lock()
|
||||||
defer p.lock.Unlock()
|
defer p.lock.Unlock()
|
||||||
|
|
||||||
p.maxTransactionsPerBlock = p.getUint32WithKey(dao, maxTransactionsPerBlockKey, defaultMaxTransactionsPerBlock)
|
p.maxTransactionsPerBlock = getUint32WithKey(p.ContractID, dao, maxTransactionsPerBlockKey, defaultMaxTransactionsPerBlock)
|
||||||
p.maxBlockSize = p.getUint32WithKey(dao, maxBlockSizeKey, defaultMaxBlockSize)
|
p.maxBlockSize = getUint32WithKey(p.ContractID, dao, maxBlockSizeKey, defaultMaxBlockSize)
|
||||||
p.feePerByte = p.getInt64WithKey(dao, feePerByteKey, defaultFeePerByte)
|
p.feePerByte = getInt64WithKey(p.ContractID, dao, feePerByteKey, defaultFeePerByte)
|
||||||
p.maxBlockSystemFee = p.getInt64WithKey(dao, maxBlockSystemFeeKey, defaultMaxBlockSystemFee)
|
p.maxBlockSystemFee = getInt64WithKey(p.ContractID, dao, maxBlockSystemFeeKey, defaultMaxBlockSystemFee)
|
||||||
p.maxVerificationGas = defaultMaxVerificationGas
|
p.maxVerificationGas = defaultMaxVerificationGas
|
||||||
|
|
||||||
p.blockedAccounts = make([]util.Uint160, 0)
|
p.blockedAccounts = make([]util.Uint160, 0)
|
||||||
|
@ -207,7 +205,7 @@ func (p *Policy) GetMaxTransactionsPerBlockInternal(dao dao.DAO) uint32 {
|
||||||
if p.isValid {
|
if p.isValid {
|
||||||
return p.maxTransactionsPerBlock
|
return p.maxTransactionsPerBlock
|
||||||
}
|
}
|
||||||
return p.getUint32WithKey(dao, maxTransactionsPerBlockKey, defaultMaxTransactionsPerBlock)
|
return getUint32WithKey(p.ContractID, dao, maxTransactionsPerBlockKey, defaultMaxTransactionsPerBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getMaxBlockSize is Policy contract method and returns maximum block size.
|
// getMaxBlockSize is Policy contract method and returns maximum block size.
|
||||||
|
@ -222,7 +220,7 @@ func (p *Policy) GetMaxBlockSizeInternal(dao dao.DAO) uint32 {
|
||||||
if p.isValid {
|
if p.isValid {
|
||||||
return p.maxBlockSize
|
return p.maxBlockSize
|
||||||
}
|
}
|
||||||
return p.getUint32WithKey(dao, maxBlockSizeKey, defaultMaxBlockSize)
|
return getUint32WithKey(p.ContractID, dao, maxBlockSizeKey, defaultMaxBlockSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getFeePerByte is Policy contract method and returns required transaction's fee
|
// getFeePerByte is Policy contract method and returns required transaction's fee
|
||||||
|
@ -238,7 +236,7 @@ func (p *Policy) GetFeePerByteInternal(dao dao.DAO) int64 {
|
||||||
if p.isValid {
|
if p.isValid {
|
||||||
return p.feePerByte
|
return p.feePerByte
|
||||||
}
|
}
|
||||||
return p.getInt64WithKey(dao, feePerByteKey, defaultFeePerByte)
|
return getInt64WithKey(p.ContractID, dao, feePerByteKey, defaultFeePerByte)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMaxVerificationGas returns maximum gas allowed to be burned during verificaion.
|
// GetMaxVerificationGas returns maximum gas allowed to be burned during verificaion.
|
||||||
|
@ -262,7 +260,7 @@ func (p *Policy) GetMaxBlockSystemFeeInternal(dao dao.DAO) int64 {
|
||||||
if p.isValid {
|
if p.isValid {
|
||||||
return p.maxBlockSystemFee
|
return p.maxBlockSystemFee
|
||||||
}
|
}
|
||||||
return p.getInt64WithKey(dao, maxBlockSystemFeeKey, defaultMaxBlockSystemFee)
|
return getInt64WithKey(p.ContractID, dao, maxBlockSystemFeeKey, defaultMaxBlockSystemFee)
|
||||||
}
|
}
|
||||||
|
|
||||||
// isBlocked is Policy contract method and checks whether provided account is blocked.
|
// isBlocked is Policy contract method and checks whether provided account is blocked.
|
||||||
|
@ -296,7 +294,7 @@ func (p *Policy) setMaxTransactionsPerBlock(ic *interop.Context, args []stackite
|
||||||
if value > block.MaxTransactionsPerBlock {
|
if value > block.MaxTransactionsPerBlock {
|
||||||
panic(fmt.Errorf("MaxTransactionsPerBlock cannot exceed the maximum allowed transactions per block = %d", block.MaxTransactionsPerBlock))
|
panic(fmt.Errorf("MaxTransactionsPerBlock cannot exceed the maximum allowed transactions per block = %d", block.MaxTransactionsPerBlock))
|
||||||
}
|
}
|
||||||
ok, err := p.checkValidators(ic)
|
ok, err := checkValidators(ic)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -305,7 +303,7 @@ func (p *Policy) setMaxTransactionsPerBlock(ic *interop.Context, args []stackite
|
||||||
}
|
}
|
||||||
p.lock.Lock()
|
p.lock.Lock()
|
||||||
defer p.lock.Unlock()
|
defer p.lock.Unlock()
|
||||||
err = p.setUint32WithKey(ic.DAO, maxTransactionsPerBlockKey, value)
|
err = setUint32WithKey(p.ContractID, ic.DAO, maxTransactionsPerBlockKey, value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -319,7 +317,7 @@ func (p *Policy) setMaxBlockSize(ic *interop.Context, args []stackitem.Item) sta
|
||||||
if value > payload.MaxSize {
|
if value > payload.MaxSize {
|
||||||
panic(fmt.Errorf("MaxBlockSize cannot be more than the maximum payload size = %d", payload.MaxSize))
|
panic(fmt.Errorf("MaxBlockSize cannot be more than the maximum payload size = %d", payload.MaxSize))
|
||||||
}
|
}
|
||||||
ok, err := p.checkValidators(ic)
|
ok, err := checkValidators(ic)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -328,7 +326,7 @@ func (p *Policy) setMaxBlockSize(ic *interop.Context, args []stackitem.Item) sta
|
||||||
}
|
}
|
||||||
p.lock.Lock()
|
p.lock.Lock()
|
||||||
defer p.lock.Unlock()
|
defer p.lock.Unlock()
|
||||||
err = p.setUint32WithKey(ic.DAO, maxBlockSizeKey, value)
|
err = setUint32WithKey(p.ContractID, ic.DAO, maxBlockSizeKey, value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -342,7 +340,7 @@ func (p *Policy) setFeePerByte(ic *interop.Context, args []stackitem.Item) stack
|
||||||
if value < 0 || value > maxFeePerByte {
|
if value < 0 || value > maxFeePerByte {
|
||||||
panic(fmt.Errorf("FeePerByte shouldn't be negative or greater than %d", maxFeePerByte))
|
panic(fmt.Errorf("FeePerByte shouldn't be negative or greater than %d", maxFeePerByte))
|
||||||
}
|
}
|
||||||
ok, err := p.checkValidators(ic)
|
ok, err := checkValidators(ic)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -351,7 +349,7 @@ func (p *Policy) setFeePerByte(ic *interop.Context, args []stackitem.Item) stack
|
||||||
}
|
}
|
||||||
p.lock.Lock()
|
p.lock.Lock()
|
||||||
defer p.lock.Unlock()
|
defer p.lock.Unlock()
|
||||||
err = p.setInt64WithKey(ic.DAO, feePerByteKey, value)
|
err = setInt64WithKey(p.ContractID, ic.DAO, feePerByteKey, value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -365,7 +363,7 @@ func (p *Policy) setMaxBlockSystemFee(ic *interop.Context, args []stackitem.Item
|
||||||
if value <= minBlockSystemFee {
|
if value <= minBlockSystemFee {
|
||||||
panic(fmt.Errorf("MaxBlockSystemFee cannot be less then %d", minBlockSystemFee))
|
panic(fmt.Errorf("MaxBlockSystemFee cannot be less then %d", minBlockSystemFee))
|
||||||
}
|
}
|
||||||
ok, err := p.checkValidators(ic)
|
ok, err := checkValidators(ic)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -374,7 +372,7 @@ func (p *Policy) setMaxBlockSystemFee(ic *interop.Context, args []stackitem.Item
|
||||||
}
|
}
|
||||||
p.lock.Lock()
|
p.lock.Lock()
|
||||||
defer p.lock.Unlock()
|
defer p.lock.Unlock()
|
||||||
err = p.setInt64WithKey(ic.DAO, maxBlockSystemFeeKey, value)
|
err = setInt64WithKey(p.ContractID, ic.DAO, maxBlockSystemFeeKey, value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -385,7 +383,7 @@ func (p *Policy) setMaxBlockSystemFee(ic *interop.Context, args []stackitem.Item
|
||||||
// blockAccount is Policy contract method and adds given account hash to the list
|
// blockAccount is Policy contract method and adds given account hash to the list
|
||||||
// of blocked accounts.
|
// of blocked accounts.
|
||||||
func (p *Policy) blockAccount(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
func (p *Policy) blockAccount(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
ok, err := p.checkValidators(ic)
|
ok, err := checkValidators(ic)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -412,7 +410,7 @@ func (p *Policy) blockAccount(ic *interop.Context, args []stackitem.Item) stacki
|
||||||
// unblockAccount is Policy contract method and removes given account hash from
|
// unblockAccount is Policy contract method and removes given account hash from
|
||||||
// the list of blocked accounts.
|
// the list of blocked accounts.
|
||||||
func (p *Policy) unblockAccount(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
func (p *Policy) unblockAccount(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
ok, err := p.checkValidators(ic)
|
ok, err := checkValidators(ic)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -434,46 +432,6 @@ func (p *Policy) unblockAccount(ic *interop.Context, args []stackitem.Item) stac
|
||||||
return stackitem.NewBool(true)
|
return stackitem.NewBool(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Policy) getUint32WithKey(dao dao.DAO, key []byte, defaultValue uint32) uint32 {
|
|
||||||
si := dao.GetStorageItem(p.ContractID, key)
|
|
||||||
if si == nil {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
return binary.LittleEndian.Uint32(si.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Policy) setUint32WithKey(dao dao.DAO, key []byte, value uint32) error {
|
|
||||||
si := &state.StorageItem{
|
|
||||||
Value: make([]byte, 4),
|
|
||||||
}
|
|
||||||
binary.LittleEndian.PutUint32(si.Value, value)
|
|
||||||
return dao.PutStorageItem(p.ContractID, key, si)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Policy) getInt64WithKey(dao dao.DAO, key []byte, defaultValue int64) int64 {
|
|
||||||
si := dao.GetStorageItem(p.ContractID, key)
|
|
||||||
if si == nil {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
return int64(binary.LittleEndian.Uint64(si.Value))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Policy) setInt64WithKey(dao dao.DAO, key []byte, value int64) error {
|
|
||||||
si := &state.StorageItem{
|
|
||||||
Value: make([]byte, 8),
|
|
||||||
}
|
|
||||||
binary.LittleEndian.PutUint64(si.Value, uint64(value))
|
|
||||||
return dao.PutStorageItem(p.ContractID, key, si)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Policy) checkValidators(ic *interop.Context) (bool, error) {
|
|
||||||
prevBlock, err := ic.Chain.GetBlock(ic.Block.PrevHash)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return runtime.CheckHashedWitness(ic, prevBlock.NextConsensus)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckPolicy checks whether transaction conforms to current policy restrictions
|
// CheckPolicy checks whether transaction conforms to current policy restrictions
|
||||||
// like not being signed by blocked account or not exceeding block-level system
|
// like not being signed by blocked account or not exceeding block-level system
|
||||||
// fee limit.
|
// fee limit.
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
package native
|
package native
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
@ -27,3 +31,43 @@ func putSerializableToDAO(id int32, d dao.DAO, key []byte, item io.Serializable)
|
||||||
Value: w.Bytes(),
|
Value: w.Bytes(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getInt64WithKey(id int32, d dao.DAO, key []byte, defaultValue int64) int64 {
|
||||||
|
si := d.GetStorageItem(id, key)
|
||||||
|
if si == nil {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
return int64(binary.LittleEndian.Uint64(si.Value))
|
||||||
|
}
|
||||||
|
|
||||||
|
func setInt64WithKey(id int32, dao dao.DAO, key []byte, value int64) error {
|
||||||
|
si := &state.StorageItem{
|
||||||
|
Value: make([]byte, 8),
|
||||||
|
}
|
||||||
|
binary.LittleEndian.PutUint64(si.Value, uint64(value))
|
||||||
|
return dao.PutStorageItem(id, key, si)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUint32WithKey(id int32, dao dao.DAO, key []byte, defaultValue uint32) uint32 {
|
||||||
|
si := dao.GetStorageItem(id, key)
|
||||||
|
if si == nil {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
return binary.LittleEndian.Uint32(si.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setUint32WithKey(id int32, dao dao.DAO, key []byte, value uint32) error {
|
||||||
|
si := &state.StorageItem{
|
||||||
|
Value: make([]byte, 4),
|
||||||
|
}
|
||||||
|
binary.LittleEndian.PutUint32(si.Value, value)
|
||||||
|
return dao.PutStorageItem(id, key, si)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkValidators(ic *interop.Context) (bool, error) {
|
||||||
|
prevBlock, err := ic.Chain.GetBlock(ic.Block.PrevHash)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return runtime.CheckHashedWitness(ic, prevBlock.NextConsensus)
|
||||||
|
}
|
||||||
|
|
|
@ -2,12 +2,14 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/internal/testchain"
|
"github.com/nspcc-dev/neo-go/internal/testchain"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
@ -319,3 +321,48 @@ func TestNotaryNodesReward(t *testing.T) {
|
||||||
checkReward(5, 7, spendDeposit)
|
checkReward(5, 7, spendDeposit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMaxNotValidBeforeDelta(t *testing.T) {
|
||||||
|
chain := newTestChain(t)
|
||||||
|
defer chain.Close()
|
||||||
|
notaryHash := chain.contracts.Notary.Hash
|
||||||
|
|
||||||
|
t.Run("get, internal method", func(t *testing.T) {
|
||||||
|
n := chain.contracts.Notary.GetMaxNotValidBeforeDelta(chain.dao)
|
||||||
|
require.Equal(t, 140, int(n))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("get, contract method", func(t *testing.T) {
|
||||||
|
res, err := invokeContractMethod(chain, 100000000, notaryHash, "getMaxNotValidBeforeDelta")
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkResult(t, res, stackitem.NewBigInteger(big.NewInt(140)))
|
||||||
|
require.NoError(t, chain.persist())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("set", func(t *testing.T) {
|
||||||
|
res, err := invokeContractMethod(chain, 100000000, notaryHash, "setMaxNotValidBeforeDelta", bigint.ToBytes(big.NewInt(150)))
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkResult(t, res, stackitem.NewBool(true))
|
||||||
|
n := chain.contracts.Notary.GetMaxNotValidBeforeDelta(chain.dao)
|
||||||
|
require.Equal(t, 150, int(n))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("set, too big value", func(t *testing.T) {
|
||||||
|
res, err := invokeContractMethod(chain, 100000000, notaryHash, "setMaxNotValidBeforeDelta", bigint.ToBytes(big.NewInt(transaction.MaxValidUntilBlockIncrement/2+1)))
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkFAULTState(t, res)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("set, too small value", func(t *testing.T) {
|
||||||
|
res, err := invokeContractMethod(chain, 100000000, notaryHash, "setMaxNotValidBeforeDelta", bigint.ToBytes(big.NewInt(int64(chain.GetConfig().ValidatorsCount-1))))
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkFAULTState(t, res)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("set, not signed by committee", func(t *testing.T) {
|
||||||
|
signer, err := wallet.NewAccount()
|
||||||
|
require.NoError(t, err)
|
||||||
|
invokeRes, err := invokeContractMethodBy(t, chain, signer, notaryHash, "setMaxNotValidBeforeDelta", bigint.ToBytes(big.NewInt(150)))
|
||||||
|
checkResult(t, invokeRes, stackitem.NewBool(false))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -45,6 +46,13 @@ func TestMaxTransactionsPerBlock(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
checkFAULTState(t, res)
|
checkFAULTState(t, res)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("set, not signed by committee", func(t *testing.T) {
|
||||||
|
signer, err := wallet.NewAccount()
|
||||||
|
require.NoError(t, err)
|
||||||
|
invokeRes, err := invokeContractMethodBy(t, chain, signer, policyHash, "setMaxTransactionsPerBlock", bigint.ToBytes(big.NewInt(1024)))
|
||||||
|
checkResult(t, invokeRes, stackitem.NewBool(false))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMaxBlockSize(t *testing.T) {
|
func TestMaxBlockSize(t *testing.T) {
|
||||||
|
@ -80,6 +88,13 @@ func TestMaxBlockSize(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
checkFAULTState(t, res)
|
checkFAULTState(t, res)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("set, not signed by committee", func(t *testing.T) {
|
||||||
|
signer, err := wallet.NewAccount()
|
||||||
|
require.NoError(t, err)
|
||||||
|
invokeRes, err := invokeContractMethodBy(t, chain, signer, policyHash, "setMaxBlockSize", bigint.ToBytes(big.NewInt(102400)))
|
||||||
|
checkResult(t, invokeRes, stackitem.NewBool(false))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFeePerByte(t *testing.T) {
|
func TestFeePerByte(t *testing.T) {
|
||||||
|
@ -119,6 +134,13 @@ func TestFeePerByte(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
checkFAULTState(t, res)
|
checkFAULTState(t, res)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("set, not signed by committee", func(t *testing.T) {
|
||||||
|
signer, err := wallet.NewAccount()
|
||||||
|
require.NoError(t, err)
|
||||||
|
invokeRes, err := invokeContractMethodBy(t, chain, signer, policyHash, "setFeePerByte", bigint.ToBytes(big.NewInt(1024)))
|
||||||
|
checkResult(t, invokeRes, stackitem.NewBool(false))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBlockSystemFee(t *testing.T) {
|
func TestBlockSystemFee(t *testing.T) {
|
||||||
|
@ -154,6 +176,13 @@ func TestBlockSystemFee(t *testing.T) {
|
||||||
checkResult(t, res, stackitem.NewBigInteger(big.NewInt(100000000)))
|
checkResult(t, res, stackitem.NewBigInteger(big.NewInt(100000000)))
|
||||||
require.NoError(t, chain.persist())
|
require.NoError(t, chain.persist())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("set, not signed by committee", func(t *testing.T) {
|
||||||
|
signer, err := wallet.NewAccount()
|
||||||
|
require.NoError(t, err)
|
||||||
|
invokeRes, err := invokeContractMethodBy(t, chain, signer, policyHash, "setMaxBlockSystemFee", bigint.ToBytes(big.NewInt(100000000)))
|
||||||
|
checkResult(t, invokeRes, stackitem.NewBool(false))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBlockedAccounts(t *testing.T) {
|
func TestBlockedAccounts(t *testing.T) {
|
||||||
|
@ -217,4 +246,11 @@ func TestBlockedAccounts(t *testing.T) {
|
||||||
checkResult(t, res, stackitem.NewBool(false))
|
checkResult(t, res, stackitem.NewBool(false))
|
||||||
require.NoError(t, chain.persist())
|
require.NoError(t, chain.persist())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("not signed by committee", func(t *testing.T) {
|
||||||
|
signer, err := wallet.NewAccount()
|
||||||
|
require.NoError(t, err)
|
||||||
|
invokeRes, err := invokeContractMethodBy(t, chain, signer, policyHash, "blockAccount", account.BytesBE())
|
||||||
|
checkResult(t, invokeRes, stackitem.NewBool(false))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/mempool"
|
"github.com/nspcc-dev/neo-go/pkg/core/mempool"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
@ -31,21 +32,30 @@ import (
|
||||||
type testChain struct {
|
type testChain struct {
|
||||||
config.ProtocolConfiguration
|
config.ProtocolConfiguration
|
||||||
*mempool.Pool
|
*mempool.Pool
|
||||||
blocksCh []chan<- *block.Block
|
blocksCh []chan<- *block.Block
|
||||||
blockheight uint32
|
blockheight uint32
|
||||||
poolTx func(*transaction.Transaction) error
|
poolTx func(*transaction.Transaction) error
|
||||||
blocks map[util.Uint256]*block.Block
|
poolTxWithData func(*transaction.Transaction, interface{}, *mempool.Pool) error
|
||||||
hdrHashes map[uint32]util.Uint256
|
blocks map[util.Uint256]*block.Block
|
||||||
txs map[util.Uint256]*transaction.Transaction
|
hdrHashes map[uint32]util.Uint256
|
||||||
|
txs map[util.Uint256]*transaction.Transaction
|
||||||
|
verifyWitnessF func() error
|
||||||
|
maxVerificationGAS int64
|
||||||
|
notaryContractScriptHash util.Uint160
|
||||||
|
notaryDepositExpiration uint32
|
||||||
|
postBlock []func(blockchainer.Blockchainer, *mempool.Pool, *block.Block)
|
||||||
|
utilityTokenBalance *big.Int
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestChain() *testChain {
|
func newTestChain() *testChain {
|
||||||
return &testChain{
|
return &testChain{
|
||||||
Pool: mempool.New(10),
|
Pool: mempool.New(10, 0),
|
||||||
poolTx: func(*transaction.Transaction) error { return nil },
|
poolTx: func(*transaction.Transaction) error { return nil },
|
||||||
blocks: make(map[util.Uint256]*block.Block),
|
poolTxWithData: func(*transaction.Transaction, interface{}, *mempool.Pool) error { return nil },
|
||||||
hdrHashes: make(map[uint32]util.Uint256),
|
blocks: make(map[util.Uint256]*block.Block),
|
||||||
txs: make(map[util.Uint256]*transaction.Transaction),
|
hdrHashes: make(map[uint32]util.Uint256),
|
||||||
|
txs: make(map[util.Uint256]*transaction.Transaction),
|
||||||
|
ProtocolConfiguration: config.ProtocolConfiguration{P2PNotaryRequestPayloadPoolSize: 10},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,6 +75,48 @@ func (chain *testChain) putTx(tx *transaction.Transaction) {
|
||||||
func (chain *testChain) ApplyPolicyToTxSet([]*transaction.Transaction) []*transaction.Transaction {
|
func (chain *testChain) ApplyPolicyToTxSet([]*transaction.Transaction) []*transaction.Transaction {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (chain *testChain) IsTxStillRelevant(t *transaction.Transaction, txpool *mempool.Pool, isPartialTx bool) bool {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (chain *testChain) GetNotaryDepositExpiration(acc util.Uint160) uint32 {
|
||||||
|
if chain.notaryDepositExpiration != 0 {
|
||||||
|
return chain.notaryDepositExpiration
|
||||||
|
}
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (chain *testChain) GetNotaryContractScriptHash() util.Uint160 {
|
||||||
|
if !chain.notaryContractScriptHash.Equals(util.Uint160{}) {
|
||||||
|
return chain.notaryContractScriptHash
|
||||||
|
}
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (chain *testChain) GetNotaryBalance(acc util.Uint160) *big.Int {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (chain *testChain) GetPolicer() blockchainer.Policer {
|
||||||
|
return chain
|
||||||
|
}
|
||||||
|
|
||||||
|
func (chain *testChain) GetMaxVerificationGAS() int64 {
|
||||||
|
if chain.maxVerificationGAS != 0 {
|
||||||
|
return chain.maxVerificationGAS
|
||||||
|
}
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (chain *testChain) PoolTxWithData(t *transaction.Transaction, data interface{}, mp *mempool.Pool, feer mempool.Feer, verificationFunction func(bc blockchainer.Blockchainer, t *transaction.Transaction, data interface{}) error) error {
|
||||||
|
return chain.poolTxWithData(t, data, mp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (chain *testChain) RegisterPostBlock(f func(blockchainer.Blockchainer, *mempool.Pool, *block.Block)) {
|
||||||
|
chain.postBlock = append(chain.postBlock, f)
|
||||||
|
}
|
||||||
|
|
||||||
func (chain *testChain) GetConfig() config.ProtocolConfiguration {
|
func (chain *testChain) GetConfig() config.ProtocolConfiguration {
|
||||||
return chain.ProtocolConfiguration
|
return chain.ProtocolConfiguration
|
||||||
}
|
}
|
||||||
|
@ -77,7 +129,7 @@ func (chain *testChain) FeePerByte() int64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chain *testChain) P2PSigExtensionsEnabled() bool {
|
func (chain *testChain) P2PSigExtensionsEnabled() bool {
|
||||||
return false
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chain *testChain) GetMaxBlockSystemFee() int64 {
|
func (chain *testChain) GetMaxBlockSystemFee() int64 {
|
||||||
|
@ -207,6 +259,9 @@ func (chain *testChain) GetGoverningTokenBalance(acc util.Uint160) (*big.Int, ui
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chain *testChain) GetUtilityTokenBalance(uint160 util.Uint160) *big.Int {
|
func (chain *testChain) GetUtilityTokenBalance(uint160 util.Uint160) *big.Int {
|
||||||
|
if chain.utilityTokenBalance != nil {
|
||||||
|
return chain.utilityTokenBalance
|
||||||
|
}
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,7 +285,10 @@ func (chain *testChain) SubscribeForTransactions(ch chan<- *transaction.Transact
|
||||||
func (chain *testChain) VerifyTx(*transaction.Transaction) error {
|
func (chain *testChain) VerifyTx(*transaction.Transaction) error {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
func (*testChain) VerifyWitness(util.Uint160, crypto.Verifiable, *transaction.Witness, int64) error {
|
func (chain *testChain) VerifyWitness(util.Uint160, crypto.Verifiable, *transaction.Witness, int64) error {
|
||||||
|
if chain.verifyWitnessF != nil {
|
||||||
|
return chain.verifyWitnessF()
|
||||||
|
}
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,18 +64,19 @@ const (
|
||||||
CMDPong CommandType = 0x19
|
CMDPong CommandType = 0x19
|
||||||
|
|
||||||
// synchronization
|
// synchronization
|
||||||
CMDGetHeaders CommandType = 0x20
|
CMDGetHeaders CommandType = 0x20
|
||||||
CMDHeaders CommandType = 0x21
|
CMDHeaders CommandType = 0x21
|
||||||
CMDGetBlocks CommandType = 0x24
|
CMDGetBlocks CommandType = 0x24
|
||||||
CMDMempool CommandType = 0x25
|
CMDMempool CommandType = 0x25
|
||||||
CMDInv CommandType = 0x27
|
CMDInv CommandType = 0x27
|
||||||
CMDGetData CommandType = 0x28
|
CMDGetData CommandType = 0x28
|
||||||
CMDGetBlockByIndex CommandType = 0x29
|
CMDGetBlockByIndex CommandType = 0x29
|
||||||
CMDNotFound CommandType = 0x2a
|
CMDNotFound CommandType = 0x2a
|
||||||
CMDTX = CommandType(payload.TXType)
|
CMDTX = CommandType(payload.TXType)
|
||||||
CMDBlock = CommandType(payload.BlockType)
|
CMDBlock = CommandType(payload.BlockType)
|
||||||
CMDConsensus = CommandType(payload.ConsensusType)
|
CMDConsensus = CommandType(payload.ConsensusType)
|
||||||
CMDReject CommandType = 0x2f
|
CMDP2PNotaryRequest = CommandType(payload.P2PNotaryRequestType)
|
||||||
|
CMDReject CommandType = 0x2f
|
||||||
|
|
||||||
// SPV protocol
|
// SPV protocol
|
||||||
CMDFilterLoad CommandType = 0x30
|
CMDFilterLoad CommandType = 0x30
|
||||||
|
@ -148,6 +149,8 @@ func (m *Message) decodePayload() error {
|
||||||
p = block.New(m.Network, m.StateRootInHeader)
|
p = block.New(m.Network, m.StateRootInHeader)
|
||||||
case CMDConsensus:
|
case CMDConsensus:
|
||||||
p = consensus.NewPayload(m.Network, m.StateRootInHeader)
|
p = consensus.NewPayload(m.Network, m.StateRootInHeader)
|
||||||
|
case CMDP2PNotaryRequest:
|
||||||
|
p = &payload.P2PNotaryRequest{Network: m.Network}
|
||||||
case CMDGetBlocks:
|
case CMDGetBlocks:
|
||||||
p = &payload.GetBlocks{}
|
p = &payload.GetBlocks{}
|
||||||
case CMDGetHeaders:
|
case CMDGetHeaders:
|
||||||
|
|
40
pkg/network/notary_feer.go
Normal file
40
pkg/network/notary_feer.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NotaryFeer implements mempool.Feer interface for Notary balance handling.
|
||||||
|
type NotaryFeer struct {
|
||||||
|
bc blockchainer.Blockchainer
|
||||||
|
}
|
||||||
|
|
||||||
|
// FeePerByte implements mempool.Feer interface.
|
||||||
|
func (f NotaryFeer) FeePerByte() int64 {
|
||||||
|
return f.bc.FeePerByte()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUtilityTokenBalance implements mempool.Feer interface.
|
||||||
|
func (f NotaryFeer) GetUtilityTokenBalance(acc util.Uint160) *big.Int {
|
||||||
|
return f.bc.GetNotaryBalance(acc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockHeight implements mempool.Feer interface.
|
||||||
|
func (f NotaryFeer) BlockHeight() uint32 {
|
||||||
|
return f.bc.BlockHeight()
|
||||||
|
}
|
||||||
|
|
||||||
|
// P2PSigExtensionsEnabled implements mempool.Feer interface.
|
||||||
|
func (f NotaryFeer) P2PSigExtensionsEnabled() bool {
|
||||||
|
return f.bc.P2PSigExtensionsEnabled()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNotaryFeer returns new NotaryFeer instance.
|
||||||
|
func NewNotaryFeer(bc blockchainer.Blockchainer) NotaryFeer {
|
||||||
|
return NotaryFeer{
|
||||||
|
bc: bc,
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,21 +20,24 @@ func (i InventoryType) String() string {
|
||||||
return "block"
|
return "block"
|
||||||
case ConsensusType:
|
case ConsensusType:
|
||||||
return "consensus"
|
return "consensus"
|
||||||
|
case P2PNotaryRequestType:
|
||||||
|
return "p2pNotaryRequest"
|
||||||
default:
|
default:
|
||||||
return "unknown inventory type"
|
return "unknown inventory type"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Valid returns true if the inventory (type) is known.
|
// Valid returns true if the inventory (type) is known.
|
||||||
func (i InventoryType) Valid() bool {
|
func (i InventoryType) Valid(p2pSigExtensionsEnabled bool) bool {
|
||||||
return i == BlockType || i == TXType || i == ConsensusType
|
return i == BlockType || i == TXType || i == ConsensusType || (p2pSigExtensionsEnabled && i == P2PNotaryRequestType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// List of valid InventoryTypes.
|
// List of valid InventoryTypes.
|
||||||
const (
|
const (
|
||||||
TXType InventoryType = 0x2b
|
TXType InventoryType = 0x2b
|
||||||
BlockType InventoryType = 0x2c
|
BlockType InventoryType = 0x2c
|
||||||
ConsensusType InventoryType = 0x2d
|
ConsensusType InventoryType = 0x2d
|
||||||
|
P2PNotaryRequestType InventoryType = 0x50
|
||||||
)
|
)
|
||||||
|
|
||||||
// Inventory payload.
|
// Inventory payload.
|
||||||
|
|
|
@ -31,15 +31,22 @@ func TestEmptyInv(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValid(t *testing.T) {
|
func TestValid(t *testing.T) {
|
||||||
require.True(t, TXType.Valid())
|
require.True(t, TXType.Valid(false))
|
||||||
require.True(t, BlockType.Valid())
|
require.True(t, TXType.Valid(true))
|
||||||
require.True(t, ConsensusType.Valid())
|
require.True(t, BlockType.Valid(false))
|
||||||
require.False(t, InventoryType(0xFF).Valid())
|
require.True(t, BlockType.Valid(true))
|
||||||
|
require.True(t, ConsensusType.Valid(false))
|
||||||
|
require.True(t, ConsensusType.Valid(true))
|
||||||
|
require.False(t, P2PNotaryRequestType.Valid(false))
|
||||||
|
require.True(t, P2PNotaryRequestType.Valid(true))
|
||||||
|
require.False(t, InventoryType(0xFF).Valid(false))
|
||||||
|
require.False(t, InventoryType(0xFF).Valid(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestString(t *testing.T) {
|
func TestString(t *testing.T) {
|
||||||
require.Equal(t, "TX", TXType.String())
|
require.Equal(t, "TX", TXType.String())
|
||||||
require.Equal(t, "block", BlockType.String())
|
require.Equal(t, "block", BlockType.String())
|
||||||
require.Equal(t, "consensus", ConsensusType.String())
|
require.Equal(t, "consensus", ConsensusType.String())
|
||||||
|
require.Equal(t, "p2pNotaryRequest", P2PNotaryRequestType.String())
|
||||||
require.True(t, strings.Contains(InventoryType(0xFF).String(), "unknown"))
|
require.True(t, strings.Contains(InventoryType(0xFF).String(), "unknown"))
|
||||||
}
|
}
|
||||||
|
|
149
pkg/network/payload/notary_request.go
Normal file
149
pkg/network/payload/notary_request.go
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
package payload
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
|
)
|
||||||
|
|
||||||
|
// P2PNotaryRequest contains main and fallback transactions for the Notary service.
|
||||||
|
type P2PNotaryRequest struct {
|
||||||
|
MainTransaction *transaction.Transaction
|
||||||
|
FallbackTransaction *transaction.Transaction
|
||||||
|
Network netmode.Magic
|
||||||
|
|
||||||
|
Witness transaction.Witness
|
||||||
|
|
||||||
|
hash util.Uint256
|
||||||
|
signedHash util.Uint256
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash returns payload's hash.
|
||||||
|
func (r *P2PNotaryRequest) Hash() util.Uint256 {
|
||||||
|
if r.hash.Equals(util.Uint256{}) {
|
||||||
|
if r.createHash() != nil {
|
||||||
|
panic("failed to compute hash!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r.hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSignedHash returns a hash of the payload used to verify it.
|
||||||
|
func (r *P2PNotaryRequest) GetSignedHash() util.Uint256 {
|
||||||
|
if r.signedHash.Equals(util.Uint256{}) {
|
||||||
|
if r.createHash() != nil {
|
||||||
|
panic("failed to compute hash!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r.signedHash
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSignedPart returns a part of the payload which must be signed.
|
||||||
|
func (r *P2PNotaryRequest) GetSignedPart() []byte {
|
||||||
|
buf := io.NewBufBinWriter()
|
||||||
|
buf.WriteU32LE(uint32(r.Network))
|
||||||
|
r.encodeHashableFields(buf.BinWriter)
|
||||||
|
if buf.Err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// createHash creates hash of the payload.
|
||||||
|
func (r *P2PNotaryRequest) createHash() error {
|
||||||
|
b := r.GetSignedPart()
|
||||||
|
if b == nil {
|
||||||
|
return errors.New("failed to serialize hashable data")
|
||||||
|
}
|
||||||
|
r.updateHashes(b)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateHashes updates Payload's hashes based on the given buffer which should
|
||||||
|
// be a signable data slice.
|
||||||
|
func (r *P2PNotaryRequest) updateHashes(b []byte) {
|
||||||
|
r.signedHash = hash.Sha256(b)
|
||||||
|
r.hash = hash.Sha256(r.signedHash.BytesBE())
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeBinaryUnsigned reads payload from w excluding signature.
|
||||||
|
func (r *P2PNotaryRequest) decodeHashableFields(br *io.BinReader) {
|
||||||
|
r.MainTransaction = new(transaction.Transaction)
|
||||||
|
r.FallbackTransaction = new(transaction.Transaction)
|
||||||
|
r.MainTransaction.DecodeBinary(br)
|
||||||
|
r.FallbackTransaction.DecodeBinary(br)
|
||||||
|
if br.Err == nil {
|
||||||
|
br.Err = r.isValid()
|
||||||
|
}
|
||||||
|
if br.Err == nil {
|
||||||
|
br.Err = r.createHash()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeBinary implements io.Serializable interface.
|
||||||
|
func (r *P2PNotaryRequest) DecodeBinary(br *io.BinReader) {
|
||||||
|
r.decodeHashableFields(br)
|
||||||
|
if br.Err == nil {
|
||||||
|
r.Witness.DecodeBinary(br)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeHashableFields writes payload to w excluding signature.
|
||||||
|
func (r *P2PNotaryRequest) encodeHashableFields(bw *io.BinWriter) {
|
||||||
|
r.MainTransaction.EncodeBinary(bw)
|
||||||
|
r.FallbackTransaction.EncodeBinary(bw)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeBinary implements Serializable interface.
|
||||||
|
func (r *P2PNotaryRequest) EncodeBinary(bw *io.BinWriter) {
|
||||||
|
r.encodeHashableFields(bw)
|
||||||
|
r.Witness.EncodeBinary(bw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *P2PNotaryRequest) isValid() error {
|
||||||
|
nKeysMain := r.MainTransaction.GetAttributes(transaction.NotaryAssistedT)
|
||||||
|
if len(nKeysMain) == 0 {
|
||||||
|
return errors.New("main transaction should have NotaryAssisted attribute")
|
||||||
|
}
|
||||||
|
if nKeysMain[0].Value.(*transaction.NotaryAssisted).NKeys == 0 {
|
||||||
|
return errors.New("main transaction should have NKeys > 0")
|
||||||
|
}
|
||||||
|
if len(r.FallbackTransaction.Signers) != 2 {
|
||||||
|
return errors.New("fallback transaction should have two signers")
|
||||||
|
}
|
||||||
|
if len(r.FallbackTransaction.Scripts) != 2 {
|
||||||
|
return errors.New("fallback transaction should have dummy Notary witness and valid witness for the second signer")
|
||||||
|
}
|
||||||
|
if len(r.FallbackTransaction.Scripts[0].InvocationScript) != 66 ||
|
||||||
|
len(r.FallbackTransaction.Scripts[0].VerificationScript) != 0 ||
|
||||||
|
!bytes.HasPrefix(r.FallbackTransaction.Scripts[0].InvocationScript, []byte{byte(opcode.PUSHDATA1), 64}) {
|
||||||
|
return errors.New("fallback transaction has invalid dummy Notary witness")
|
||||||
|
}
|
||||||
|
if !r.FallbackTransaction.HasAttribute(transaction.NotValidBeforeT) {
|
||||||
|
return errors.New("fallback transactions should have NotValidBefore attribute")
|
||||||
|
}
|
||||||
|
conflicts := r.FallbackTransaction.GetAttributes(transaction.ConflictsT)
|
||||||
|
if len(conflicts) != 1 {
|
||||||
|
return errors.New("fallback transaction should have one Conflicts attribute")
|
||||||
|
}
|
||||||
|
if conflicts[0].Value.(*transaction.Conflicts).Hash != r.MainTransaction.Hash() {
|
||||||
|
return errors.New("fallback transaction does not conflicts with the main transaction")
|
||||||
|
}
|
||||||
|
nKeysFallback := r.FallbackTransaction.GetAttributes(transaction.NotaryAssistedT)
|
||||||
|
if len(nKeysFallback) == 0 {
|
||||||
|
return errors.New("fallback transaction should have NotaryAssisted attribute")
|
||||||
|
}
|
||||||
|
if nKeysFallback[0].Value.(*transaction.NotaryAssisted).NKeys != 0 {
|
||||||
|
return errors.New("fallback transaction should have NKeys = 0")
|
||||||
|
}
|
||||||
|
if r.MainTransaction.ValidUntilBlock != r.FallbackTransaction.ValidUntilBlock {
|
||||||
|
return errors.New("both main and fallback transactions should have the same ValidUntil value")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
184
pkg/network/payload/notary_request_test.go
Normal file
184
pkg/network/payload/notary_request_test.go
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
package payload
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/internal/random"
|
||||||
|
"github.com/nspcc-dev/neo-go/internal/testserdes"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNotaryRequestIsValid(t *testing.T) {
|
||||||
|
mainTx := &transaction.Transaction{
|
||||||
|
Attributes: []transaction.Attribute{{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{NKeys: 1}}},
|
||||||
|
Script: []byte{0, 1, 2},
|
||||||
|
ValidUntilBlock: 123,
|
||||||
|
}
|
||||||
|
errorCases := map[string]*P2PNotaryRequest{
|
||||||
|
"main tx: missing NotaryAssisted attribute": {MainTransaction: &transaction.Transaction{}},
|
||||||
|
"main tx: zero NKeys": {MainTransaction: &transaction.Transaction{Attributes: []transaction.Attribute{{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{NKeys: 0}}}}},
|
||||||
|
"fallback transaction: invalid signers count": {
|
||||||
|
MainTransaction: mainTx,
|
||||||
|
FallbackTransaction: &transaction.Transaction{Signers: []transaction.Signer{{Account: random.Uint160()}}},
|
||||||
|
},
|
||||||
|
"fallback transaction: invalid witnesses count": {
|
||||||
|
MainTransaction: mainTx,
|
||||||
|
FallbackTransaction: &transaction.Transaction{Signers: []transaction.Signer{{Account: random.Uint160()}}},
|
||||||
|
},
|
||||||
|
"fallback tx: invalid dummy Notary witness (bad witnesses length)": {
|
||||||
|
MainTransaction: mainTx,
|
||||||
|
FallbackTransaction: &transaction.Transaction{
|
||||||
|
Signers: []transaction.Signer{{Account: random.Uint160()}, {Account: random.Uint160()}},
|
||||||
|
Scripts: []transaction.Witness{{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"fallback tx: invalid dummy Notary witness (bad invocation script length)": {
|
||||||
|
MainTransaction: mainTx,
|
||||||
|
FallbackTransaction: &transaction.Transaction{
|
||||||
|
Signers: []transaction.Signer{{Account: random.Uint160()}, {Account: random.Uint160()}},
|
||||||
|
Scripts: []transaction.Witness{{}, {}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"fallback tx: invalid dummy Notary witness (bad invocation script prefix)": {
|
||||||
|
MainTransaction: mainTx,
|
||||||
|
FallbackTransaction: &transaction.Transaction{
|
||||||
|
Signers: []transaction.Signer{{Account: random.Uint160()}, {Account: random.Uint160()}},
|
||||||
|
Scripts: []transaction.Witness{{InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 65}, make([]byte, 64, 64)...)}, {}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"fallback tx: invalid dummy Notary witness (non-empty verification script))": {
|
||||||
|
MainTransaction: mainTx,
|
||||||
|
FallbackTransaction: &transaction.Transaction{
|
||||||
|
Signers: []transaction.Signer{{Account: random.Uint160()}, {Account: random.Uint160()}},
|
||||||
|
Scripts: []transaction.Witness{{InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, make([]byte, 64, 64)...), VerificationScript: make([]byte, 1, 1)}, {}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"fallback tx: missing NotValidBefore attribute": {
|
||||||
|
MainTransaction: mainTx,
|
||||||
|
FallbackTransaction: &transaction.Transaction{
|
||||||
|
Signers: []transaction.Signer{{Account: random.Uint160()}, {Account: random.Uint160()}},
|
||||||
|
Scripts: []transaction.Witness{{InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, make([]byte, 64, 64)...), VerificationScript: make([]byte, 0)}, {}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"fallback tx: invalid number of Conflicts attributes": {
|
||||||
|
MainTransaction: mainTx,
|
||||||
|
FallbackTransaction: &transaction.Transaction{
|
||||||
|
Attributes: []transaction.Attribute{{Type: transaction.NotValidBeforeT, Value: &transaction.NotValidBefore{Height: 123}}},
|
||||||
|
Signers: []transaction.Signer{{Account: random.Uint160()}, {Account: random.Uint160()}},
|
||||||
|
Scripts: []transaction.Witness{{InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, make([]byte, 64, 64)...), VerificationScript: make([]byte, 0)}, {}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"fallback tx: does not conflicts with main tx": {
|
||||||
|
MainTransaction: mainTx,
|
||||||
|
FallbackTransaction: &transaction.Transaction{
|
||||||
|
Attributes: []transaction.Attribute{
|
||||||
|
{Type: transaction.NotValidBeforeT, Value: &transaction.NotValidBefore{Height: 123}},
|
||||||
|
{Type: transaction.ConflictsT, Value: &transaction.Conflicts{Hash: util.Uint256{}}},
|
||||||
|
},
|
||||||
|
Signers: []transaction.Signer{{Account: random.Uint160()}, {Account: random.Uint160()}},
|
||||||
|
Scripts: []transaction.Witness{{InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, make([]byte, 64, 64)...), VerificationScript: make([]byte, 0)}, {}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"fallback tx: missing NotaryAssisted attribute": {
|
||||||
|
MainTransaction: mainTx,
|
||||||
|
FallbackTransaction: &transaction.Transaction{
|
||||||
|
Attributes: []transaction.Attribute{
|
||||||
|
{Type: transaction.NotValidBeforeT, Value: &transaction.NotValidBefore{Height: 123}},
|
||||||
|
{Type: transaction.ConflictsT, Value: &transaction.Conflicts{Hash: mainTx.Hash()}},
|
||||||
|
},
|
||||||
|
Signers: []transaction.Signer{{Account: random.Uint160()}, {Account: random.Uint160()}},
|
||||||
|
Scripts: []transaction.Witness{{InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, make([]byte, 64, 64)...), VerificationScript: make([]byte, 0)}, {}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"fallback tx: non-zero NKeys": {
|
||||||
|
MainTransaction: mainTx,
|
||||||
|
FallbackTransaction: &transaction.Transaction{
|
||||||
|
Attributes: []transaction.Attribute{
|
||||||
|
{Type: transaction.NotValidBeforeT, Value: &transaction.NotValidBefore{Height: 123}},
|
||||||
|
{Type: transaction.ConflictsT, Value: &transaction.Conflicts{Hash: mainTx.Hash()}},
|
||||||
|
{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{NKeys: 1}},
|
||||||
|
},
|
||||||
|
Signers: []transaction.Signer{{Account: random.Uint160()}, {Account: random.Uint160()}},
|
||||||
|
Scripts: []transaction.Witness{{InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, make([]byte, 64, 64)...), VerificationScript: make([]byte, 0)}, {}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"fallback tx: ValidUntilBlock mismatch": {
|
||||||
|
MainTransaction: mainTx,
|
||||||
|
FallbackTransaction: &transaction.Transaction{
|
||||||
|
ValidUntilBlock: 321,
|
||||||
|
Attributes: []transaction.Attribute{
|
||||||
|
{Type: transaction.NotValidBeforeT, Value: &transaction.NotValidBefore{Height: 123}},
|
||||||
|
{Type: transaction.ConflictsT, Value: &transaction.Conflicts{Hash: mainTx.Hash()}},
|
||||||
|
{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{NKeys: 0}},
|
||||||
|
},
|
||||||
|
Signers: []transaction.Signer{{Account: random.Uint160()}, {Account: random.Uint160()}},
|
||||||
|
Scripts: []transaction.Witness{{InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, make([]byte, 64, 64)...), VerificationScript: make([]byte, 0)}, {}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, errCase := range errorCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
require.Error(t, errCase.isValid())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
t.Run("good", func(t *testing.T) {
|
||||||
|
p := &P2PNotaryRequest{
|
||||||
|
MainTransaction: mainTx,
|
||||||
|
FallbackTransaction: &transaction.Transaction{
|
||||||
|
ValidUntilBlock: 123,
|
||||||
|
Attributes: []transaction.Attribute{
|
||||||
|
{Type: transaction.NotValidBeforeT, Value: &transaction.NotValidBefore{Height: 123}},
|
||||||
|
{Type: transaction.ConflictsT, Value: &transaction.Conflicts{Hash: mainTx.Hash()}},
|
||||||
|
{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{NKeys: 0}},
|
||||||
|
},
|
||||||
|
Signers: []transaction.Signer{{Account: random.Uint160()}, {Account: random.Uint160()}},
|
||||||
|
Scripts: []transaction.Witness{{InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, make([]byte, 64, 64)...), VerificationScript: make([]byte, 0)}, {}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
require.NoError(t, p.isValid())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNotaryRequestEncodeDecodeBinary(t *testing.T) {
|
||||||
|
mainTx := &transaction.Transaction{
|
||||||
|
Attributes: []transaction.Attribute{{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{NKeys: 1}}},
|
||||||
|
Script: []byte{0, 1, 2},
|
||||||
|
ValidUntilBlock: 123,
|
||||||
|
Signers: []transaction.Signer{{Account: util.Uint160{1, 5, 9}}},
|
||||||
|
Scripts: []transaction.Witness{{
|
||||||
|
InvocationScript: []byte{1, 4, 7},
|
||||||
|
VerificationScript: []byte{3, 6, 9},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
_ = mainTx.Hash()
|
||||||
|
_ = mainTx.Size()
|
||||||
|
fallbackTx := &transaction.Transaction{
|
||||||
|
Script: []byte{3, 2, 1},
|
||||||
|
ValidUntilBlock: 123,
|
||||||
|
Attributes: []transaction.Attribute{
|
||||||
|
{Type: transaction.NotValidBeforeT, Value: &transaction.NotValidBefore{Height: 123}},
|
||||||
|
{Type: transaction.ConflictsT, Value: &transaction.Conflicts{Hash: mainTx.Hash()}},
|
||||||
|
{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{NKeys: 0}},
|
||||||
|
},
|
||||||
|
Signers: []transaction.Signer{{Account: util.Uint160{1, 4, 7}}, {Account: util.Uint160{9, 8, 7}}},
|
||||||
|
Scripts: []transaction.Witness{
|
||||||
|
{InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, make([]byte, 64, 64)...), VerificationScript: make([]byte, 0)},
|
||||||
|
{InvocationScript: []byte{1, 2, 3}, VerificationScript: []byte{1, 2, 3}}},
|
||||||
|
}
|
||||||
|
_ = fallbackTx.Hash()
|
||||||
|
_ = fallbackTx.Size()
|
||||||
|
p := &P2PNotaryRequest{
|
||||||
|
MainTransaction: mainTx,
|
||||||
|
FallbackTransaction: fallbackTx,
|
||||||
|
Witness: transaction.Witness{
|
||||||
|
InvocationScript: []byte{1, 2, 3},
|
||||||
|
VerificationScript: []byte{7, 8, 9},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
require.Equal(t, hash.Sha256(p.GetSignedHash().BytesBE()), p.Hash())
|
||||||
|
testserdes.EncodeDecodeBinary(t, p, new(P2PNotaryRequest))
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core"
|
"github.com/nspcc-dev/neo-go/pkg/core"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
|
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/mempool"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/network/capability"
|
"github.com/nspcc-dev/neo-go/pkg/network/capability"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
||||||
|
@ -59,11 +60,13 @@ type (
|
||||||
// stateRootInHeader specifies if block header contain state root.
|
// stateRootInHeader specifies if block header contain state root.
|
||||||
stateRootInHeader bool
|
stateRootInHeader bool
|
||||||
|
|
||||||
transport Transporter
|
transport Transporter
|
||||||
discovery Discoverer
|
discovery Discoverer
|
||||||
chain blockchainer.Blockchainer
|
chain blockchainer.Blockchainer
|
||||||
bQueue *blockQueue
|
bQueue *blockQueue
|
||||||
consensus consensus.Service
|
consensus consensus.Service
|
||||||
|
notaryRequestPool *mempool.Pool
|
||||||
|
NotaryFeer NotaryFeer
|
||||||
|
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
peers map[Peer]bool
|
peers map[Peer]bool
|
||||||
|
@ -124,6 +127,15 @@ func newServerFromConstructors(config ServerConfig, chain blockchainer.Blockchai
|
||||||
log: log,
|
log: log,
|
||||||
transactions: make(chan *transaction.Transaction, 64),
|
transactions: make(chan *transaction.Transaction, 64),
|
||||||
}
|
}
|
||||||
|
if chain.P2PSigExtensionsEnabled() {
|
||||||
|
s.NotaryFeer = NewNotaryFeer(chain)
|
||||||
|
s.notaryRequestPool = mempool.New(chain.GetConfig().P2PNotaryRequestPayloadPoolSize, 1)
|
||||||
|
chain.RegisterPostBlock(func(bc blockchainer.Blockchainer, txpool *mempool.Pool, _ *block.Block) {
|
||||||
|
s.notaryRequestPool.RemoveStale(func(t *transaction.Transaction) bool {
|
||||||
|
return bc.IsTxStillRelevant(t, txpool, true)
|
||||||
|
}, s.NotaryFeer)
|
||||||
|
})
|
||||||
|
}
|
||||||
s.bQueue = newBlockQueue(maxBlockBatch, chain, log, func(b *block.Block) {
|
s.bQueue = newBlockQueue(maxBlockBatch, chain, log, func(b *block.Block) {
|
||||||
if !s.consensusStarted.Load() {
|
if !s.consensusStarted.Load() {
|
||||||
s.tryStartConsensus()
|
s.tryStartConsensus()
|
||||||
|
@ -188,7 +200,7 @@ func (s *Server) Start(errChan chan error) {
|
||||||
zap.Uint32("headerHeight", s.chain.HeaderHeight()))
|
zap.Uint32("headerHeight", s.chain.HeaderHeight()))
|
||||||
|
|
||||||
s.tryStartConsensus()
|
s.tryStartConsensus()
|
||||||
s.initStaleTxMemPool()
|
s.initStaleMemPools()
|
||||||
|
|
||||||
go s.broadcastTxLoop()
|
go s.broadcastTxLoop()
|
||||||
go s.relayBlocksLoop()
|
go s.relayBlocksLoop()
|
||||||
|
@ -507,6 +519,9 @@ func (s *Server) handleInvCmd(p Peer, inv *payload.Inventory) error {
|
||||||
cp := s.consensus.GetPayload(h)
|
cp := s.consensus.GetPayload(h)
|
||||||
return cp != nil
|
return cp != nil
|
||||||
},
|
},
|
||||||
|
payload.P2PNotaryRequestType: func(h util.Uint256) bool {
|
||||||
|
return s.notaryRequestPool.ContainsKey(h)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if exists := typExists[inv.Type]; exists != nil {
|
if exists := typExists[inv.Type]; exists != nil {
|
||||||
for _, hash := range inv.Hashes {
|
for _, hash := range inv.Hashes {
|
||||||
|
@ -573,6 +588,12 @@ func (s *Server) handleGetDataCmd(p Peer, inv *payload.Inventory) error {
|
||||||
if cp := s.consensus.GetPayload(hash); cp != nil {
|
if cp := s.consensus.GetPayload(hash); cp != nil {
|
||||||
msg = NewMessage(CMDConsensus, cp)
|
msg = NewMessage(CMDConsensus, cp)
|
||||||
}
|
}
|
||||||
|
case payload.P2PNotaryRequestType:
|
||||||
|
if nrp, ok := s.notaryRequestPool.TryGetData(hash); ok { // already have checked P2PSigExtEnabled
|
||||||
|
msg = NewMessage(CMDP2PNotaryRequest, nrp.(*payload.P2PNotaryRequest))
|
||||||
|
} else {
|
||||||
|
notFound = append(notFound, hash)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if msg != nil {
|
if msg != nil {
|
||||||
pkt, err := msg.Bytes()
|
pkt, err := msg.Bytes()
|
||||||
|
@ -687,11 +708,62 @@ func (s *Server) handleTxCmd(tx *transaction.Transaction) error {
|
||||||
// in the pool.
|
// in the pool.
|
||||||
if s.verifyAndPoolTX(tx) == RelaySucceed {
|
if s.verifyAndPoolTX(tx) == RelaySucceed {
|
||||||
s.consensus.OnTransaction(tx)
|
s.consensus.OnTransaction(tx)
|
||||||
s.broadcastTX(tx)
|
s.broadcastTX(tx, nil)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleP2PNotaryRequestCmd process received P2PNotaryRequest payload.
|
||||||
|
func (s *Server) handleP2PNotaryRequestCmd(r *payload.P2PNotaryRequest) error {
|
||||||
|
if !s.chain.P2PSigExtensionsEnabled() {
|
||||||
|
return errors.New("P2PNotaryRequestCMD was received, but P2PSignatureExtensions are disabled")
|
||||||
|
}
|
||||||
|
if s.verifyAndPoolNotaryRequest(r) == RelaySucceed {
|
||||||
|
s.broadcastP2PNotaryRequestPayload(nil, r)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyAndPoolNotaryRequest verifies NotaryRequest payload and adds it to the payload mempool.
|
||||||
|
func (s *Server) verifyAndPoolNotaryRequest(r *payload.P2PNotaryRequest) RelayReason {
|
||||||
|
if err := s.chain.PoolTxWithData(r.FallbackTransaction, r, s.notaryRequestPool, s.NotaryFeer, verifyNotaryRequest); err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, core.ErrAlreadyExists):
|
||||||
|
return RelayAlreadyExists
|
||||||
|
case errors.Is(err, core.ErrOOM):
|
||||||
|
return RelayOutOfMemory
|
||||||
|
case errors.Is(err, core.ErrPolicy):
|
||||||
|
return RelayPolicyFail
|
||||||
|
default:
|
||||||
|
return RelayInvalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return RelaySucceed
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyNotaryRequest is a function for state-dependant P2PNotaryRequest payload verification which is executed before ordinary blockchain's verification.
|
||||||
|
func verifyNotaryRequest(bc blockchainer.Blockchainer, _ *transaction.Transaction, data interface{}) error {
|
||||||
|
r := data.(*payload.P2PNotaryRequest)
|
||||||
|
payer := r.FallbackTransaction.Signers[1].Account
|
||||||
|
if err := bc.VerifyWitness(payer, r, &r.Witness, bc.GetPolicer().GetMaxVerificationGAS()); err != nil {
|
||||||
|
return fmt.Errorf("bad P2PNotaryRequest payload witness: %w", err)
|
||||||
|
}
|
||||||
|
if r.FallbackTransaction.Sender() != bc.GetNotaryContractScriptHash() {
|
||||||
|
return errors.New("P2PNotary contract should be a sender of the fallback transaction")
|
||||||
|
}
|
||||||
|
depositExpiration := bc.GetNotaryDepositExpiration(payer)
|
||||||
|
if r.FallbackTransaction.ValidUntilBlock >= depositExpiration {
|
||||||
|
return fmt.Errorf("fallback transaction is valid after deposit is unlocked: ValidUntilBlock is %d, deposit lock expires at %d", r.FallbackTransaction.ValidUntilBlock, depositExpiration)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) broadcastP2PNotaryRequestPayload(_ *transaction.Transaction, data interface{}) {
|
||||||
|
r := data.(payload.P2PNotaryRequest) // we can guarantee that cast is successful
|
||||||
|
msg := NewMessage(CMDInv, payload.NewInventory(payload.P2PNotaryRequestType, []util.Uint256{r.FallbackTransaction.Hash()}))
|
||||||
|
s.broadcastMessage(msg)
|
||||||
|
}
|
||||||
|
|
||||||
// handleAddrCmd will process received addresses.
|
// handleAddrCmd will process received addresses.
|
||||||
func (s *Server) handleAddrCmd(p Peer, addrs *payload.AddressList) error {
|
func (s *Server) handleAddrCmd(p Peer, addrs *payload.AddressList) error {
|
||||||
if !p.CanProcessAddr() {
|
if !p.CanProcessAddr() {
|
||||||
|
@ -770,7 +842,7 @@ func (s *Server) handleMessage(peer Peer, msg *Message) error {
|
||||||
|
|
||||||
if peer.Handshaked() {
|
if peer.Handshaked() {
|
||||||
if inv, ok := msg.Payload.(*payload.Inventory); ok {
|
if inv, ok := msg.Payload.(*payload.Inventory); ok {
|
||||||
if !inv.Type.Valid() || len(inv.Hashes) == 0 {
|
if !inv.Type.Valid(s.chain.P2PSigExtensionsEnabled()) || len(inv.Hashes) == 0 {
|
||||||
return errInvalidInvType
|
return errInvalidInvType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -808,6 +880,9 @@ func (s *Server) handleMessage(peer Peer, msg *Message) error {
|
||||||
case CMDTX:
|
case CMDTX:
|
||||||
tx := msg.Payload.(*transaction.Transaction)
|
tx := msg.Payload.(*transaction.Transaction)
|
||||||
return s.handleTxCmd(tx)
|
return s.handleTxCmd(tx)
|
||||||
|
case CMDP2PNotaryRequest:
|
||||||
|
r := msg.Payload.(*payload.P2PNotaryRequest)
|
||||||
|
return s.handleP2PNotaryRequestCmd(r)
|
||||||
case CMDPing:
|
case CMDPing:
|
||||||
ping := msg.Payload.(*payload.Ping)
|
ping := msg.Payload.(*payload.Ping)
|
||||||
return s.handlePing(peer, ping)
|
return s.handlePing(peer, ping)
|
||||||
|
@ -943,13 +1018,13 @@ func (s *Server) verifyAndPoolTX(t *transaction.Transaction) RelayReason {
|
||||||
func (s *Server) RelayTxn(t *transaction.Transaction) RelayReason {
|
func (s *Server) RelayTxn(t *transaction.Transaction) RelayReason {
|
||||||
ret := s.verifyAndPoolTX(t)
|
ret := s.verifyAndPoolTX(t)
|
||||||
if ret == RelaySucceed {
|
if ret == RelaySucceed {
|
||||||
s.broadcastTX(t)
|
s.broadcastTX(t, nil)
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// broadcastTX broadcasts an inventory message about new transaction.
|
// broadcastTX broadcasts an inventory message about new transaction.
|
||||||
func (s *Server) broadcastTX(t *transaction.Transaction) {
|
func (s *Server) broadcastTX(t *transaction.Transaction, _ interface{}) {
|
||||||
select {
|
select {
|
||||||
case s.transactions <- t:
|
case s.transactions <- t:
|
||||||
case <-s.quit:
|
case <-s.quit:
|
||||||
|
@ -964,8 +1039,8 @@ func (s *Server) broadcastTxHashes(hs []util.Uint256) {
|
||||||
s.iteratePeersWithSendMsg(msg, Peer.EnqueuePacket, Peer.IsFullNode)
|
s.iteratePeersWithSendMsg(msg, Peer.EnqueuePacket, Peer.IsFullNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// initStaleTxMemPool initializes mempool for stale tx processing.
|
// initStaleMemPools initializes mempools for stale tx/payload processing.
|
||||||
func (s *Server) initStaleTxMemPool() {
|
func (s *Server) initStaleMemPools() {
|
||||||
cfg := s.chain.GetConfig()
|
cfg := s.chain.GetConfig()
|
||||||
threshold := 5
|
threshold := 5
|
||||||
if cfg.ValidatorsCount*2 > threshold {
|
if cfg.ValidatorsCount*2 > threshold {
|
||||||
|
@ -974,6 +1049,9 @@ func (s *Server) initStaleTxMemPool() {
|
||||||
|
|
||||||
mp := s.chain.GetMemPool()
|
mp := s.chain.GetMemPool()
|
||||||
mp.SetResendThreshold(uint32(threshold), s.broadcastTX)
|
mp.SetResendThreshold(uint32(threshold), s.broadcastTX)
|
||||||
|
if s.chain.P2PSigExtensionsEnabled() {
|
||||||
|
s.notaryRequestPool.SetResendThreshold(uint32(threshold), s.broadcastP2PNotaryRequestPayload)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// broadcastTxLoop is a loop for batching and sending
|
// broadcastTxLoop is a loop for batching and sending
|
||||||
|
|
|
@ -19,6 +19,8 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/network/capability"
|
"github.com/nspcc-dev/neo-go/pkg/network/capability"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
|
@ -445,7 +447,7 @@ func (s *Server) testHandleGetData(t *testing.T, invType payload.InventoryType,
|
||||||
p.handshaked = true
|
p.handshaked = true
|
||||||
p.messageHandler = func(t *testing.T, msg *Message) {
|
p.messageHandler = func(t *testing.T, msg *Message) {
|
||||||
switch msg.Command {
|
switch msg.Command {
|
||||||
case CMDTX, CMDBlock, CMDConsensus:
|
case CMDTX, CMDBlock, CMDConsensus, CMDP2PNotaryRequest:
|
||||||
require.Equal(t, found, msg.Payload)
|
require.Equal(t, found, msg.Payload)
|
||||||
recvResponse.Store(true)
|
recvResponse.Store(true)
|
||||||
case CMDNotFound:
|
case CMDNotFound:
|
||||||
|
@ -463,6 +465,7 @@ func (s *Server) testHandleGetData(t *testing.T, invType payload.InventoryType,
|
||||||
func TestGetData(t *testing.T) {
|
func TestGetData(t *testing.T) {
|
||||||
s, shutdown := startTestServer(t)
|
s, shutdown := startTestServer(t)
|
||||||
defer shutdown()
|
defer shutdown()
|
||||||
|
s.chain.(*testChain).utilityTokenBalance = big.NewInt(1000000)
|
||||||
|
|
||||||
t.Run("block", func(t *testing.T) {
|
t.Run("block", func(t *testing.T) {
|
||||||
b := newDummyBlock(2, 0)
|
b := newDummyBlock(2, 0)
|
||||||
|
@ -478,6 +481,44 @@ func TestGetData(t *testing.T) {
|
||||||
notFound := []util.Uint256{hs[0], hs[2]}
|
notFound := []util.Uint256{hs[0], hs[2]}
|
||||||
s.testHandleGetData(t, payload.TXType, hs, notFound, tx)
|
s.testHandleGetData(t, payload.TXType, hs, notFound, tx)
|
||||||
})
|
})
|
||||||
|
t.Run("p2pNotaryRequest", func(t *testing.T) {
|
||||||
|
mainTx := &transaction.Transaction{
|
||||||
|
Attributes: []transaction.Attribute{{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{NKeys: 1}}},
|
||||||
|
Script: []byte{0, 1, 2},
|
||||||
|
ValidUntilBlock: 123,
|
||||||
|
Signers: []transaction.Signer{{Account: random.Uint160()}},
|
||||||
|
Scripts: []transaction.Witness{{InvocationScript: []byte{1, 2, 3}, VerificationScript: []byte{1, 2, 3}}},
|
||||||
|
}
|
||||||
|
mainTx.Size()
|
||||||
|
mainTx.Hash()
|
||||||
|
fallbackTx := &transaction.Transaction{
|
||||||
|
Script: []byte{1, 2, 3},
|
||||||
|
ValidUntilBlock: 123,
|
||||||
|
Attributes: []transaction.Attribute{
|
||||||
|
{Type: transaction.NotValidBeforeT, Value: &transaction.NotValidBefore{Height: 123}},
|
||||||
|
{Type: transaction.ConflictsT, Value: &transaction.Conflicts{Hash: mainTx.Hash()}},
|
||||||
|
{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{NKeys: 0}},
|
||||||
|
},
|
||||||
|
Signers: []transaction.Signer{{Account: random.Uint160()}, {Account: random.Uint160()}},
|
||||||
|
Scripts: []transaction.Witness{{InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, make([]byte, 64, 64)...), VerificationScript: make([]byte, 0)}, {InvocationScript: []byte{}, VerificationScript: []byte{}}},
|
||||||
|
}
|
||||||
|
fallbackTx.Size()
|
||||||
|
fallbackTx.Hash()
|
||||||
|
r := &payload.P2PNotaryRequest{
|
||||||
|
MainTransaction: mainTx,
|
||||||
|
FallbackTransaction: fallbackTx,
|
||||||
|
Network: netmode.UnitTestNet,
|
||||||
|
Witness: transaction.Witness{
|
||||||
|
InvocationScript: []byte{1, 2, 3},
|
||||||
|
VerificationScript: []byte{1, 2, 3},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
r.Hash()
|
||||||
|
require.NoError(t, s.notaryRequestPool.Add(r.FallbackTransaction, s.chain, r))
|
||||||
|
hs := []util.Uint256{random.Uint256(), r.FallbackTransaction.Hash(), random.Uint256()}
|
||||||
|
notFound := []util.Uint256{hs[0], hs[2]}
|
||||||
|
s.testHandleGetData(t, payload.P2PNotaryRequestType, hs, notFound, r)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func initGetBlocksTest(t *testing.T) (*Server, func(), []*block.Block) {
|
func initGetBlocksTest(t *testing.T) (*Server, func(), []*block.Block) {
|
||||||
|
@ -602,6 +643,7 @@ func TestGetHeaders(t *testing.T) {
|
||||||
func TestInv(t *testing.T) {
|
func TestInv(t *testing.T) {
|
||||||
s, shutdown := startTestServer(t)
|
s, shutdown := startTestServer(t)
|
||||||
defer shutdown()
|
defer shutdown()
|
||||||
|
s.chain.(*testChain).utilityTokenBalance = big.NewInt(10000000)
|
||||||
|
|
||||||
var actual []util.Uint256
|
var actual []util.Uint256
|
||||||
p := newLocalPeer(t, s)
|
p := newLocalPeer(t, s)
|
||||||
|
@ -632,6 +674,24 @@ func TestInv(t *testing.T) {
|
||||||
})
|
})
|
||||||
require.Equal(t, []util.Uint256{hs[0], hs[2]}, actual)
|
require.Equal(t, []util.Uint256{hs[0], hs[2]}, actual)
|
||||||
})
|
})
|
||||||
|
t.Run("p2pNotaryRequest", func(t *testing.T) {
|
||||||
|
fallbackTx := transaction.New(netmode.UnitTestNet, random.Bytes(100), 123)
|
||||||
|
fallbackTx.Signers = []transaction.Signer{{Account: random.Uint160()}, {Account: random.Uint160()}}
|
||||||
|
fallbackTx.Size()
|
||||||
|
fallbackTx.Hash()
|
||||||
|
r := &payload.P2PNotaryRequest{
|
||||||
|
MainTransaction: newDummyTx(),
|
||||||
|
FallbackTransaction: fallbackTx,
|
||||||
|
Network: netmode.UnitTestNet,
|
||||||
|
}
|
||||||
|
require.NoError(t, s.notaryRequestPool.Add(r.FallbackTransaction, s.chain, r))
|
||||||
|
hs := []util.Uint256{random.Uint256(), r.FallbackTransaction.Hash(), random.Uint256()}
|
||||||
|
s.testHandleMessage(t, p, CMDInv, &payload.Inventory{
|
||||||
|
Type: payload.P2PNotaryRequestType,
|
||||||
|
Hashes: hs,
|
||||||
|
})
|
||||||
|
require.Equal(t, []util.Uint256{hs[0], hs[2]}, actual)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRequestTx(t *testing.T) {
|
func TestRequestTx(t *testing.T) {
|
||||||
|
@ -763,3 +823,43 @@ func TestMemPool(t *testing.T) {
|
||||||
s.testHandleMessage(t, p, CMDMempool, payload.NullPayload{})
|
s.testHandleMessage(t, p, CMDMempool, payload.NullPayload{})
|
||||||
require.ElementsMatch(t, expected, actual)
|
require.ElementsMatch(t, expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVerifyNotaryRequest(t *testing.T) {
|
||||||
|
bc := newTestChain()
|
||||||
|
bc.maxVerificationGAS = 10
|
||||||
|
bc.notaryContractScriptHash = util.Uint160{1, 2, 3}
|
||||||
|
newNotaryRequest := func() *payload.P2PNotaryRequest {
|
||||||
|
return &payload.P2PNotaryRequest{
|
||||||
|
MainTransaction: &transaction.Transaction{Script: []byte{0, 1, 2}},
|
||||||
|
FallbackTransaction: &transaction.Transaction{
|
||||||
|
ValidUntilBlock: 321,
|
||||||
|
Signers: []transaction.Signer{{Account: bc.notaryContractScriptHash}, {Account: random.Uint160()}},
|
||||||
|
},
|
||||||
|
Witness: transaction.Witness{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("bad payload witness", func(t *testing.T) {
|
||||||
|
bc.verifyWitnessF = func() error { return errors.New("bad witness") }
|
||||||
|
require.Error(t, verifyNotaryRequest(bc, nil, newNotaryRequest()))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad fallback sender", func(t *testing.T) {
|
||||||
|
bc.verifyWitnessF = func() error { return nil }
|
||||||
|
r := newNotaryRequest()
|
||||||
|
r.FallbackTransaction.Signers[0] = transaction.Signer{Account: util.Uint160{7, 8, 9}}
|
||||||
|
require.Error(t, verifyNotaryRequest(bc, nil, r))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("expired deposit", func(t *testing.T) {
|
||||||
|
r := newNotaryRequest()
|
||||||
|
bc.notaryDepositExpiration = r.FallbackTransaction.ValidUntilBlock
|
||||||
|
require.Error(t, verifyNotaryRequest(bc, nil, r))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("good", func(t *testing.T) {
|
||||||
|
r := newNotaryRequest()
|
||||||
|
bc.notaryDepositExpiration = r.FallbackTransaction.ValidUntilBlock + 1
|
||||||
|
require.NoError(t, verifyNotaryRequest(bc, nil, r))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue