mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-23 03:38:35 +00:00
network: add notary request payload
This commit is contained in:
parent
501c0c93c6
commit
0b5cf78468
24 changed files with 1396 additions and 232 deletions
|
@ -9,6 +9,9 @@ type (
|
|||
ProtocolConfiguration struct {
|
||||
Magic netmode.Magic `yaml:"Magic"`
|
||||
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.
|
||||
// If true, DB size will be smaller, but older roots won't be accessible.
|
||||
// This value should remain the same for the same database.
|
||||
|
@ -17,7 +20,7 @@ type (
|
|||
RemoveUntraceableBlocks bool `yaml:"RemoveUntraceableBlocks"`
|
||||
// MaxTraceableBlocks is the length of the chain accessible to smart contracts.
|
||||
MaxTraceableBlocks uint32 `yaml:"MaxTraceableBlocks"`
|
||||
// P2PSigExtensions enables additional signature-related transaction attributes
|
||||
// P2PSigExtensions enables additional signature-related logic.
|
||||
P2PSigExtensions bool `yaml:"P2PSigExtensions"`
|
||||
// ReservedAttributes allows to have reserved attributes range for experimental or private purposes.
|
||||
ReservedAttributes bool `yaml:"ReservedAttributes"`
|
||||
|
|
|
@ -418,7 +418,7 @@ func (s *service) verifyBlock(b block.Block) bool {
|
|||
s.log.Warn("proposed block has already outdated")
|
||||
return false
|
||||
}
|
||||
maxBlockSize := int(s.Chain.GetMaxBlockSize())
|
||||
maxBlockSize := int(s.Chain.GetPolicer().GetMaxBlockSize())
|
||||
size := io.GetVarSize(coreb)
|
||||
if size > maxBlockSize {
|
||||
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 pool = mempool.New(len(coreb.Transactions))
|
||||
var pool = mempool.New(len(coreb.Transactions), 0)
|
||||
var mainPool = s.Chain.GetMemPool()
|
||||
for _, tx := range coreb.Transactions {
|
||||
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 {
|
||||
s.log.Warn("proposed block system fee exceeds policy max block system fee",
|
||||
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}))
|
||||
})
|
||||
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)
|
||||
tx := transaction.New(netmode.UnitTestNet, script, 100000)
|
||||
tx.ValidUntilBlock = 1
|
||||
|
@ -407,7 +407,7 @@ func TestVerifyBlock(t *testing.T) {
|
|||
t.Run("bad big sys fee", func(t *testing.T) {
|
||||
txes := make([]*transaction.Transaction, 2)
|
||||
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
|
||||
addSender(t, 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/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/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/mempool"
|
||||
|
@ -42,9 +43,10 @@ const (
|
|||
headerBatchCount = 2000
|
||||
version = "0.1.0"
|
||||
|
||||
defaultMemPoolSize = 50000
|
||||
defaultMaxTraceableBlocks = 2102400 // 1 year of 15s blocks
|
||||
verificationGasLimit = 100000000 // 1 GAS
|
||||
defaultMemPoolSize = 50000
|
||||
defaultP2PNotaryRequestPayloadPoolSize = 1000
|
||||
defaultMaxTraceableBlocks = 2102400 // 1 year of 15s blocks
|
||||
verificationGasLimit = 100000000 // 1 GAS
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -116,6 +118,10 @@ type Blockchain struct {
|
|||
|
||||
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
|
||||
|
||||
log *zap.Logger
|
||||
|
@ -151,6 +157,10 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration, log *zap.L
|
|||
cfg.MemPoolSize = defaultMemPoolSize
|
||||
log.Info("mempool size is not set or wrong, setting default value", zap.Int("MemPoolSize", cfg.MemPoolSize))
|
||||
}
|
||||
if cfg.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 {
|
||||
cfg.MaxTraceableBlocks = defaultMaxTraceableBlocks
|
||||
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),
|
||||
stopCh: make(chan struct{}),
|
||||
runToExitCh: make(chan struct{}),
|
||||
memPool: mempool.New(cfg.MemPoolSize),
|
||||
memPool: mempool.New(cfg.MemPoolSize, 0),
|
||||
sbCommittee: committee,
|
||||
log: log,
|
||||
events: make(chan bcEvent),
|
||||
|
@ -452,7 +462,7 @@ func (bc *Blockchain) AddBlock(block *block.Block) error {
|
|||
if !block.MerkleRoot.Equals(merkle) {
|
||||
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 {
|
||||
var err error
|
||||
// Transactions are verified before adding them
|
||||
|
@ -464,7 +474,7 @@ func (bc *Blockchain) AddBlock(block *block.Block) error {
|
|||
continue
|
||||
}
|
||||
} else {
|
||||
err = bc.verifyAndPoolTx(tx, mp)
|
||||
err = bc.verifyAndPoolTx(tx, mp, bc)
|
||||
}
|
||||
if err != nil && bc.config.VerifyTransactions {
|
||||
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()
|
||||
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 {
|
||||
bc.lock.Unlock()
|
||||
return err
|
||||
|
@ -733,7 +750,10 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
|||
}
|
||||
bc.topBlock.Store(block)
|
||||
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()
|
||||
|
||||
updateBlockHeightMetric(block.Index)
|
||||
|
@ -934,6 +954,24 @@ func (bc *Blockchain) GetGoverningTokenBalance(acc util.Uint160) (*big.Int, uint
|
|||
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.
|
||||
func (bc *Blockchain) LastBatch() *storage.MemBatch {
|
||||
return bc.lastBatch
|
||||
|
@ -1204,16 +1242,6 @@ func (bc *Blockchain) FeePerByte() int64 {
|
|||
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.
|
||||
func (bc *Blockchain) GetMemPool() *mempool.Pool {
|
||||
return bc.memPool
|
||||
|
@ -1279,9 +1307,10 @@ var (
|
|||
|
||||
// verifyAndPoolTx verifies whether a transaction is bonafide or not and tries
|
||||
// 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()
|
||||
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)
|
||||
}
|
||||
// Policying.
|
||||
|
@ -1316,14 +1345,14 @@ func (bc *Blockchain) verifyAndPoolTx(t *transaction.Transaction, pool *mempool.
|
|||
return err
|
||||
}
|
||||
}
|
||||
err := bc.verifyTxWitnesses(t, nil)
|
||||
err := bc.verifyTxWitnesses(t, nil, isPartialTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := bc.verifyTxAttributes(t); err != nil {
|
||||
if err := bc.verifyTxAttributes(t, isPartialTx); err != nil {
|
||||
return err
|
||||
}
|
||||
err = pool.Add(t, bc)
|
||||
err = pool.Add(t, feer, data...)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, mempool.ErrConflict):
|
||||
|
@ -1344,7 +1373,7 @@ func (bc *Blockchain) verifyAndPoolTx(t *transaction.Transaction, pool *mempool.
|
|||
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 {
|
||||
switch attrType := tx.Attributes[i].Type; attrType {
|
||||
case transaction.HighPriority:
|
||||
|
@ -1384,9 +1413,19 @@ func (bc *Blockchain) verifyTxAttributes(tx *transaction.Transaction) error {
|
|||
if !bc.config.P2PSigExtensions {
|
||||
return fmt.Errorf("%w: NotValidBefore attribute was found, but P2PSigExtensions are disabled", ErrInvalidAttribute)
|
||||
}
|
||||
nvb := tx.Attributes[i].Value.(*transaction.NotValidBefore)
|
||||
if height := bc.BlockHeight(); height < nvb.Height {
|
||||
return fmt.Errorf("%w: transaction is not yet valid: NotValidBefore = %d, current height = %d", ErrInvalidAttribute, nvb.Height, height)
|
||||
nvb := tx.Attributes[i].Value.(*transaction.NotValidBefore).Height
|
||||
if isPartialTx {
|
||||
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:
|
||||
if !bc.config.P2PSigExtensions {
|
||||
|
@ -1412,13 +1451,13 @@ func (bc *Blockchain) verifyTxAttributes(tx *transaction.Transaction) error {
|
|||
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
|
||||
// (passed via txpool) and does witness reverification for non-standard
|
||||
// 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
|
||||
// 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 curheight = bc.BlockHeight()
|
||||
|
||||
|
@ -1432,7 +1471,7 @@ func (bc *Blockchain) isTxStillRelevant(t *transaction.Transaction, txpool *memp
|
|||
} else if txpool.HasConflicts(t, bc) {
|
||||
return false
|
||||
}
|
||||
if err := bc.verifyTxAttributes(t); err != nil {
|
||||
if err := bc.verifyTxAttributes(t, isPartialTx); err != nil {
|
||||
return false
|
||||
}
|
||||
for i := range t.Scripts {
|
||||
|
@ -1442,7 +1481,7 @@ func (bc *Blockchain) isTxStillRelevant(t *transaction.Transaction, txpool *memp
|
|||
}
|
||||
}
|
||||
if recheckWitness {
|
||||
return bc.verifyTxWitnesses(t, nil) == nil
|
||||
return bc.verifyTxWitnesses(t, nil, isPartialTx) == nil
|
||||
}
|
||||
return true
|
||||
|
||||
|
@ -1525,10 +1564,10 @@ func (bc *Blockchain) verifyStateRootWitness(r *state.MPTRoot) error {
|
|||
// current blockchain state. Note that this verification is completely isolated
|
||||
// from the main node's mempool.
|
||||
func (bc *Blockchain) VerifyTx(t *transaction.Transaction) error {
|
||||
var mp = mempool.New(1)
|
||||
var mp = mempool.New(1, 0)
|
||||
bc.lock.RLock()
|
||||
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
|
||||
|
@ -1545,7 +1584,21 @@ func (bc *Blockchain) PoolTx(t *transaction.Transaction, pools ...*mempool.Pool)
|
|||
if len(pools) == 1 {
|
||||
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.
|
||||
|
@ -1689,7 +1742,7 @@ func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transa
|
|||
// is used for easy interop access and can be omitted for transactions that are
|
||||
// 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).
|
||||
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) {
|
||||
return fmt.Errorf("%w: %d vs %d", ErrTxInvalidWitnessNum, len(t.Signers), len(t.Scripts))
|
||||
}
|
||||
|
@ -1704,7 +1757,8 @@ func (bc *Blockchain) verifyTxWitnesses(t *transaction.Transaction, block *block
|
|||
}
|
||||
for i := range t.Signers {
|
||||
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)
|
||||
}
|
||||
gasLimit -= gasConsumed
|
||||
|
@ -1757,3 +1811,33 @@ func (bc *Blockchain) newInteropContext(trigger trigger.Type, d dao.DAO, block *
|
|||
func (bc *Blockchain) P2PSigExtensionsEnabled() bool {
|
||||
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/netmode"
|
||||
"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/fee"
|
||||
"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))
|
||||
})
|
||||
t.Run("MemPoolOOM", func(t *testing.T) {
|
||||
bc.memPool = mempool.New(1)
|
||||
bc.memPool = mempool.New(1, 0)
|
||||
tx1 := bc.newTestTx(h, testScript)
|
||||
tx1.NetworkFee += 10000 // Give it more priority.
|
||||
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) {
|
||||
|
@ -1021,9 +1112,9 @@ func TestIsTxStillRelevant(t *testing.T) {
|
|||
tx := newTx(t)
|
||||
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.False(t, bc.isTxStillRelevant(tx, nil))
|
||||
require.False(t, bc.IsTxStillRelevant(tx, nil, false))
|
||||
})
|
||||
|
||||
t.Run("tx is already persisted", func(t *testing.T) {
|
||||
|
@ -1031,9 +1122,9 @@ func TestIsTxStillRelevant(t *testing.T) {
|
|||
tx.ValidUntilBlock = bc.BlockHeight() + 2
|
||||
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.False(t, bc.isTxStillRelevant(tx, nil))
|
||||
require.False(t, bc.IsTxStillRelevant(tx, nil, false))
|
||||
})
|
||||
|
||||
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.True(t, bc.isTxStillRelevant(tx1, mp))
|
||||
require.NoError(t, bc.verifyAndPoolTx(tx2, mp))
|
||||
require.False(t, bc.isTxStillRelevant(tx1, mp))
|
||||
require.True(t, bc.IsTxStillRelevant(tx1, mp, false))
|
||||
require.NoError(t, bc.verifyAndPoolTx(tx2, mp, bc))
|
||||
require.False(t, bc.IsTxStillRelevant(tx1, mp, false))
|
||||
})
|
||||
t.Run("NotValidBefore", func(t *testing.T) {
|
||||
tx3 := newTx(t)
|
||||
|
@ -1060,9 +1151,9 @@ func TestIsTxStillRelevant(t *testing.T) {
|
|||
tx3.ValidUntilBlock = bc.BlockHeight() + 2
|
||||
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.True(t, bc.isTxStillRelevant(tx3, nil))
|
||||
require.True(t, bc.IsTxStillRelevant(tx3, nil, false))
|
||||
})
|
||||
t.Run("contract witness check fails", func(t *testing.T) {
|
||||
src := fmt.Sprintf(`package verify
|
||||
|
@ -1087,9 +1178,9 @@ func TestIsTxStillRelevant(t *testing.T) {
|
|||
require.NoError(t, testchain.SignTx(bc, tx))
|
||||
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.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
|
||||
CalculateClaimable(h util.Uint160, endHeight uint32) (*big.Int, error)
|
||||
Close()
|
||||
IsTxStillRelevant(t *transaction.Transaction, txpool *mempool.Pool, isPartialTx bool) bool
|
||||
HeaderHeight() uint32
|
||||
GetBlock(hash util.Uint256) (*block.Block, error)
|
||||
GetCommittee() (keys.PublicKeys, error)
|
||||
|
@ -40,9 +41,13 @@ type Blockchainer interface {
|
|||
HasBlock(util.Uint256) bool
|
||||
HasTransaction(util.Uint256) bool
|
||||
GetAppExecResults(util.Uint256, trigger.Type) ([]state.AppExecResult, error)
|
||||
GetNotaryDepositExpiration(acc util.Uint160) uint32
|
||||
GetNativeContractScriptHash(string) (util.Uint160, error)
|
||||
GetNextBlockValidators() ([]*keys.PublicKey, error)
|
||||
GetNEP17Balances(util.Uint160) *state.NEP17Balances
|
||||
GetNotaryContractScriptHash() util.Uint160
|
||||
GetNotaryBalance(acc util.Uint160) *big.Int
|
||||
GetPolicer() Policer
|
||||
GetValidators() ([]*keys.PublicKey, error)
|
||||
GetStandByCommittee() keys.PublicKeys
|
||||
GetStandByValidators() keys.PublicKeys
|
||||
|
@ -53,9 +58,9 @@ type Blockchainer interface {
|
|||
GetTestVM(tx *transaction.Transaction, b *block.Block) *vm.VM
|
||||
GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error)
|
||||
mempool.Feer // fee interface
|
||||
GetMaxBlockSize() uint32
|
||||
GetMaxBlockSystemFee() int64
|
||||
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)
|
||||
SubscribeForExecutions(ch chan<- *state.AppExecResult)
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
transferTx := newNEP17Transfer(tokenHash, testchain.MultisigScriptHash(), to, amount, additionalArgs...)
|
||||
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) {
|
||||
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, expected, result.Stack[0])
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ var (
|
|||
type item struct {
|
||||
txn *transaction.Transaction
|
||||
blockStamp uint32
|
||||
data interface{}
|
||||
}
|
||||
|
||||
// items is a slice of item.
|
||||
|
@ -64,9 +65,10 @@ type Pool struct {
|
|||
|
||||
capacity int
|
||||
feePerByte int64
|
||||
payerIndex int
|
||||
|
||||
resendThreshold uint32
|
||||
resendFunc func(*transaction.Transaction)
|
||||
resendFunc func(*transaction.Transaction, interface{})
|
||||
}
|
||||
|
||||
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
|
||||
// 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 {
|
||||
senderFee, ok := mp.fees[tx.Sender()]
|
||||
payer := tx.Signers[mp.payerIndex].Account
|
||||
senderFee, ok := mp.fees[payer]
|
||||
if !ok {
|
||||
senderFee.balance = feer.GetUtilityTokenBalance(tx.Sender())
|
||||
senderFee.balance = feer.GetUtilityTokenBalance(payer)
|
||||
senderFee.feeSum = big.NewInt(0)
|
||||
mp.fees[tx.Sender()] = senderFee
|
||||
mp.fees[payer] = senderFee
|
||||
}
|
||||
if needCheck {
|
||||
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.
|
||||
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{
|
||||
txn: t,
|
||||
blockStamp: fee.BlockHeight(),
|
||||
}
|
||||
if data != nil {
|
||||
pItem.data = data[0]
|
||||
}
|
||||
mp.lock.Lock()
|
||||
if mp.containsKey(t.Hash()) {
|
||||
mp.lock.Unlock()
|
||||
|
@ -281,14 +287,16 @@ func (mp *Pool) removeInternal(hash util.Uint256, feer Feer) {
|
|||
break
|
||||
}
|
||||
}
|
||||
itm := mp.verifiedTxes[num]
|
||||
if num < len(mp.verifiedTxes)-1 {
|
||||
mp.verifiedTxes = append(mp.verifiedTxes[:num], mp.verifiedTxes[num+1:]...)
|
||||
} else if num == len(mp.verifiedTxes)-1 {
|
||||
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))
|
||||
mp.fees[tx.Sender()] = senderFee
|
||||
mp.fees[payer] = senderFee
|
||||
if feer.P2PSigExtensionsEnabled() {
|
||||
// remove all conflicting hashes from mp.conflicts list
|
||||
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)
|
||||
}
|
||||
height := feer.BlockHeight()
|
||||
var staleTxs []*transaction.Transaction
|
||||
var staleItems []item
|
||||
for _, itm := range mp.verifiedTxes {
|
||||
if isOK(itm.txn) && mp.checkPolicy(itm.txn, policyChanged) && mp.tryAddSendersFee(itm.txn, feer, true) {
|
||||
newVerifiedTxes = append(newVerifiedTxes, itm)
|
||||
|
@ -325,11 +333,11 @@ func (mp *Pool) RemoveStale(isOK func(*transaction.Transaction) bool, feer Feer)
|
|||
}
|
||||
}
|
||||
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.
|
||||
diff := (height - itm.blockStamp)
|
||||
if diff%mp.resendThreshold == 0 && bits.OnesCount32(diff/mp.resendThreshold) == 1 {
|
||||
staleTxs = append(staleTxs, itm.txn)
|
||||
staleItems = append(staleItems, itm)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -339,8 +347,8 @@ func (mp *Pool) RemoveStale(isOK func(*transaction.Transaction) bool, feer Feer)
|
|||
}
|
||||
}
|
||||
}
|
||||
if len(staleTxs) != 0 {
|
||||
go mp.resendStaleTxs(staleTxs)
|
||||
if len(staleItems) != 0 {
|
||||
go mp.resendStaleItems(staleItems)
|
||||
}
|
||||
mp.verifiedTxes = newVerifiedTxes
|
||||
mp.lock.Unlock()
|
||||
|
@ -366,11 +374,12 @@ func (mp *Pool) checkPolicy(tx *transaction.Transaction, policyChanged bool) boo
|
|||
}
|
||||
|
||||
// New returns a new Pool struct.
|
||||
func New(capacity int) *Pool {
|
||||
func New(capacity int, payerIndex int) *Pool {
|
||||
return &Pool{
|
||||
verifiedMap: make(map[util.Uint256]*transaction.Transaction),
|
||||
verifiedTxes: make([]item, 0, capacity),
|
||||
capacity: capacity,
|
||||
payerIndex: payerIndex,
|
||||
fees: make(map[util.Uint160]utilityBalanceAndFees),
|
||||
conflicts: make(map[util.Uint256][]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
|
||||
// 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()
|
||||
defer mp.lock.Unlock()
|
||||
mp.resendThreshold = h
|
||||
mp.resendFunc = f
|
||||
}
|
||||
|
||||
func (mp *Pool) resendStaleTxs(txs []*transaction.Transaction) {
|
||||
for i := range txs {
|
||||
mp.resendFunc(txs[i])
|
||||
func (mp *Pool) resendStaleItems(items []item) {
|
||||
for i := range items {
|
||||
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
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (mp *Pool) GetVerifiedTransactions() []*transaction.Transaction {
|
||||
mp.lock.RLock()
|
||||
|
@ -420,9 +453,10 @@ func (mp *Pool) GetVerifiedTransactions() []*transaction.Transaction {
|
|||
// checkTxConflicts is an internal unprotected version of Verify. It takes into
|
||||
// consideration conflicting transactions which are about to be removed from mempool.
|
||||
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 {
|
||||
actualSenderFee.balance = fee.GetUtilityTokenBalance(tx.Sender())
|
||||
actualSenderFee.balance = fee.GetUtilityTokenBalance(payer)
|
||||
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 {
|
||||
for _, hash := range conflictingHashes {
|
||||
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())
|
||||
}
|
||||
conflictsToBeRemoved = append(conflictsToBeRemoved, existingTx)
|
||||
|
@ -447,7 +481,7 @@ func (mp *Pool) checkTxConflicts(tx *transaction.Transaction, fee Feer) ([]*tran
|
|||
if !ok {
|
||||
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())
|
||||
}
|
||||
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),
|
||||
}
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/internal/random"
|
||||
"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/network/payload"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -20,10 +21,9 @@ type FeerStub struct {
|
|||
feePerByte int64
|
||||
p2pSigExt bool
|
||||
blockHeight uint32
|
||||
balance int64
|
||||
}
|
||||
|
||||
var balance = big.NewInt(10000000)
|
||||
|
||||
func (fs *FeerStub) FeePerByte() int64 {
|
||||
return fs.feePerByte
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ func (fs *FeerStub) BlockHeight() uint32 {
|
|||
}
|
||||
|
||||
func (fs *FeerStub) GetUtilityTokenBalance(uint160 util.Uint160) *big.Int {
|
||||
return balance
|
||||
return big.NewInt(fs.balance)
|
||||
}
|
||||
|
||||
func (fs *FeerStub) P2PSigExtensionsEnabled() bool {
|
||||
|
@ -41,7 +41,7 @@ func (fs *FeerStub) P2PSigExtensionsEnabled() bool {
|
|||
}
|
||||
|
||||
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.Nonce = 0
|
||||
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) {
|
||||
mp := New(5)
|
||||
mp := New(5, 0)
|
||||
txs := make([]*transaction.Transaction, 5)
|
||||
for i := range txs {
|
||||
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)
|
||||
f := func(tx *transaction.Transaction) {
|
||||
f := func(tx *transaction.Transaction, _ interface{}) {
|
||||
staleTxs <- tx
|
||||
}
|
||||
mp.SetResendThreshold(5, f)
|
||||
|
@ -111,9 +111,9 @@ func TestMemPoolAddRemove(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestOverCapacity(t *testing.T) {
|
||||
var fs = &FeerStub{}
|
||||
var fs = &FeerStub{balance: 10000000}
|
||||
const mempoolSize = 10
|
||||
mp := New(mempoolSize)
|
||||
mp := New(mempoolSize, 0)
|
||||
|
||||
for i := 0; i < mempoolSize; i++ {
|
||||
tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
||||
|
@ -186,7 +186,7 @@ func TestOverCapacity(t *testing.T) {
|
|||
func TestGetVerified(t *testing.T) {
|
||||
var fs = &FeerStub{}
|
||||
const mempoolSize = 10
|
||||
mp := New(mempoolSize)
|
||||
mp := New(mempoolSize, 0)
|
||||
|
||||
txes := make([]*transaction.Transaction, 0, mempoolSize)
|
||||
for i := 0; i < mempoolSize; i++ {
|
||||
|
@ -210,7 +210,7 @@ func TestGetVerified(t *testing.T) {
|
|||
func TestRemoveStale(t *testing.T) {
|
||||
var fs = &FeerStub{}
|
||||
const mempoolSize = 10
|
||||
mp := New(mempoolSize)
|
||||
mp := New(mempoolSize, 0)
|
||||
|
||||
txes1 := 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) {
|
||||
mp := New(10)
|
||||
mp := New(10, 0)
|
||||
fs := &FeerStub{balance: 10000000}
|
||||
sender0 := util.Uint160{1, 2, 3}
|
||||
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}}
|
||||
// insufficient funds to add transaction, and balance shouldn't be stored
|
||||
require.Equal(t, false, mp.Verify(tx0, &FeerStub{}))
|
||||
require.Error(t, mp.Add(tx0, &FeerStub{}))
|
||||
require.Equal(t, false, mp.Verify(tx0, fs))
|
||||
require.Error(t, mp.Add(tx0, fs))
|
||||
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
|
||||
tx1 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
||||
tx1.NetworkFee = balancePart.Int64()
|
||||
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, utilityBalanceAndFees{
|
||||
balance: balance,
|
||||
balance: big.NewInt(fs.balance),
|
||||
feeSum: big.NewInt(tx1.NetworkFee),
|
||||
}, mp.fees[sender0])
|
||||
|
||||
// balance shouldn't change after adding one more transaction
|
||||
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}}
|
||||
require.NoError(t, mp.Add(tx2, &FeerStub{}))
|
||||
require.NoError(t, mp.Add(tx2, fs))
|
||||
require.Equal(t, 2, len(mp.verifiedTxes))
|
||||
require.Equal(t, 1, len(mp.fees))
|
||||
require.Equal(t, utilityBalanceAndFees{
|
||||
balance: balance,
|
||||
feeSum: balance,
|
||||
balance: big.NewInt(fs.balance),
|
||||
feeSum: big.NewInt(fs.balance),
|
||||
}, mp.fees[sender0])
|
||||
|
||||
// can't add more transactions as we don't have enough GAS
|
||||
tx3 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
||||
tx3.NetworkFee = 1
|
||||
tx3.Signers = []transaction.Signer{{Account: sender0}}
|
||||
require.Equal(t, false, mp.Verify(tx3, &FeerStub{}))
|
||||
require.Error(t, mp.Add(tx3, &FeerStub{}))
|
||||
require.Equal(t, false, mp.Verify(tx3, fs))
|
||||
require.Error(t, mp.Add(tx3, fs))
|
||||
require.Equal(t, 1, len(mp.fees))
|
||||
require.Equal(t, utilityBalanceAndFees{
|
||||
balance: balance,
|
||||
feeSum: balance,
|
||||
balance: big.NewInt(fs.balance),
|
||||
feeSum: big.NewInt(fs.balance),
|
||||
}, mp.fees[sender0])
|
||||
|
||||
// check whether sender's fee updates correctly
|
||||
|
@ -295,10 +296,10 @@ func TestMemPoolFees(t *testing.T) {
|
|||
return true
|
||||
}
|
||||
return false
|
||||
}, &FeerStub{})
|
||||
}, fs)
|
||||
require.Equal(t, 1, len(mp.fees))
|
||||
require.Equal(t, utilityBalanceAndFees{
|
||||
balance: balance,
|
||||
balance: big.NewInt(fs.balance),
|
||||
feeSum: big.NewInt(tx2.NetworkFee),
|
||||
}, mp.fees[sender0])
|
||||
|
||||
|
@ -308,12 +309,13 @@ func TestMemPoolFees(t *testing.T) {
|
|||
return true
|
||||
}
|
||||
return false
|
||||
}, &FeerStub{})
|
||||
}, fs)
|
||||
require.Equal(t, 0, len(mp.fees))
|
||||
}
|
||||
|
||||
func TestMempoolItemsOrder(t *testing.T) {
|
||||
sender0 := util.Uint160{1, 2, 3}
|
||||
balance := big.NewInt(10000000)
|
||||
|
||||
tx1 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
||||
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) {
|
||||
mp := New(5)
|
||||
mp := New(5, 0)
|
||||
nonce := uint32(0)
|
||||
fs := &FeerStub{}
|
||||
fs := &FeerStub{balance: 10000}
|
||||
newTx := func(netFee int64, id uint64) *transaction.Transaction {
|
||||
tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
||||
tx.NetworkFee = netFee
|
||||
|
@ -406,9 +408,9 @@ func TestMempoolAddRemoveOracleResponse(t *testing.T) {
|
|||
|
||||
func TestMempoolAddRemoveConflicts(t *testing.T) {
|
||||
capacity := 6
|
||||
mp := New(capacity)
|
||||
mp := New(capacity, 0)
|
||||
var (
|
||||
fs = &FeerStub{p2pSigExt: true}
|
||||
fs = &FeerStub{p2pSigExt: true, balance: 100000}
|
||||
nonce uint32 = 1
|
||||
)
|
||||
getConflictsTx := func(netFee int64, hashes ...util.Uint256) *transaction.Transaction {
|
||||
|
@ -524,3 +526,116 @@ func TestMempoolAddRemoveConflicts(t *testing.T) {
|
|||
require.Equal(t, false, ok)
|
||||
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"
|
||||
"math"
|
||||
"math/big"
|
||||
"sync"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
|
@ -26,17 +27,29 @@ type Notary struct {
|
|||
interop.ContractMD
|
||||
GAS *GAS
|
||||
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 (
|
||||
notaryName = "Notary"
|
||||
notaryContractID = reservedContractID - 1
|
||||
// NotaryVerificationPrice is the price of `verify` Notary method.
|
||||
NotaryVerificationPrice = 100_0000
|
||||
|
||||
// prefixDeposit is a prefix for storing Notary deposits.
|
||||
prefixDeposit = 1
|
||||
defaultDepositDeltaTill = 5760
|
||||
prefixDeposit = 1
|
||||
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.
|
||||
func newNotary() *Notary {
|
||||
n := &Notary{ContractMD: *interop.NewContractMD(notaryName)}
|
||||
|
@ -76,6 +89,15 @@ func newNotary() *Notary {
|
|||
md = newMethodAndPrice(n.verify, 100_0000, smartcontract.AllowStates)
|
||||
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)
|
||||
md = newMethodAndPrice(getOnPersistWrapper(n.OnPersist), 0, smartcontract.AllowModifyStates)
|
||||
n.AddMethod(md, desc)
|
||||
|
@ -94,6 +116,8 @@ func (n *Notary) Metadata() *interop.ContractMD {
|
|||
|
||||
// Initialize initializes Notary native contract and implements Contract interface.
|
||||
func (n *Notary) Initialize(ic *interop.Context) error {
|
||||
n.isValid = true
|
||||
n.maxNotValidBeforeDelta = defaultMaxNotValidBeforeDelta
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -116,7 +140,7 @@ func (n *Notary) OnPersist(ic *interop.Context) error {
|
|||
nFees += int64(nKeys) + 1
|
||||
if tx.Sender() == n.Hash {
|
||||
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))
|
||||
if balance.Amount.Sign() == 0 {
|
||||
err := n.removeDepositFor(ic.DAO, payer.Account)
|
||||
|
@ -142,6 +166,19 @@ func (n *Notary) OnPersist(ic *interop.Context) error {
|
|||
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
|
||||
// till the specified chain's height.
|
||||
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
|
||||
currentHeight := ic.Chain.BlockHeight()
|
||||
deposit := n.getDepositFor(ic.DAO, to)
|
||||
deposit := n.GetDepositFor(ic.DAO, to)
|
||||
till := toUint32(additionalParams[1])
|
||||
if till < 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() {
|
||||
return stackitem.NewBool(false)
|
||||
}
|
||||
deposit := n.getDepositFor(ic.DAO, addr)
|
||||
deposit := n.GetDepositFor(ic.DAO, addr)
|
||||
if deposit == nil {
|
||||
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{}) {
|
||||
to = toUint160(args[1])
|
||||
}
|
||||
deposit := n.getDepositFor(ic.DAO, from)
|
||||
deposit := n.GetDepositFor(ic.DAO, from)
|
||||
if deposit == nil {
|
||||
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.
|
||||
func (n *Notary) balanceOf(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
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 {
|
||||
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.
|
||||
func (n *Notary) expirationOf(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
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 {
|
||||
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.
|
||||
|
@ -302,7 +349,7 @@ func (n *Notary) verify(ic *interop.Context, args []stackitem.Item) stackitem.It
|
|||
return stackitem.NewBool(false)
|
||||
}
|
||||
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 {
|
||||
return stackitem.NewBool(false)
|
||||
}
|
||||
|
@ -328,9 +375,47 @@ func (n *Notary) GetNotaryNodes(d dao.DAO) (keys.PublicKeys, error) {
|
|||
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.
|
||||
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()...)
|
||||
deposit := new(state.Deposit)
|
||||
err := getSerializableFromDAO(n.ContractID, dao, key, deposit)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package native
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sort"
|
||||
|
@ -10,7 +9,6 @@ import (
|
|||
"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/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/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
||||
|
@ -167,10 +165,10 @@ func (p *Policy) OnPersistEnd(dao dao.DAO) error {
|
|||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
p.maxTransactionsPerBlock = p.getUint32WithKey(dao, maxTransactionsPerBlockKey, defaultMaxTransactionsPerBlock)
|
||||
p.maxBlockSize = p.getUint32WithKey(dao, maxBlockSizeKey, defaultMaxBlockSize)
|
||||
p.feePerByte = p.getInt64WithKey(dao, feePerByteKey, defaultFeePerByte)
|
||||
p.maxBlockSystemFee = p.getInt64WithKey(dao, maxBlockSystemFeeKey, defaultMaxBlockSystemFee)
|
||||
p.maxTransactionsPerBlock = getUint32WithKey(p.ContractID, dao, maxTransactionsPerBlockKey, defaultMaxTransactionsPerBlock)
|
||||
p.maxBlockSize = getUint32WithKey(p.ContractID, dao, maxBlockSizeKey, defaultMaxBlockSize)
|
||||
p.feePerByte = getInt64WithKey(p.ContractID, dao, feePerByteKey, defaultFeePerByte)
|
||||
p.maxBlockSystemFee = getInt64WithKey(p.ContractID, dao, maxBlockSystemFeeKey, defaultMaxBlockSystemFee)
|
||||
p.maxVerificationGas = defaultMaxVerificationGas
|
||||
|
||||
p.blockedAccounts = make([]util.Uint160, 0)
|
||||
|
@ -207,7 +205,7 @@ func (p *Policy) GetMaxTransactionsPerBlockInternal(dao dao.DAO) uint32 {
|
|||
if p.isValid {
|
||||
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.
|
||||
|
@ -222,7 +220,7 @@ func (p *Policy) GetMaxBlockSizeInternal(dao dao.DAO) uint32 {
|
|||
if p.isValid {
|
||||
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
|
||||
|
@ -238,7 +236,7 @@ func (p *Policy) GetFeePerByteInternal(dao dao.DAO) int64 {
|
|||
if p.isValid {
|
||||
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.
|
||||
|
@ -262,7 +260,7 @@ func (p *Policy) GetMaxBlockSystemFeeInternal(dao dao.DAO) int64 {
|
|||
if p.isValid {
|
||||
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.
|
||||
|
@ -296,7 +294,7 @@ func (p *Policy) setMaxTransactionsPerBlock(ic *interop.Context, args []stackite
|
|||
if value > 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 {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -305,7 +303,7 @@ func (p *Policy) setMaxTransactionsPerBlock(ic *interop.Context, args []stackite
|
|||
}
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
err = p.setUint32WithKey(ic.DAO, maxTransactionsPerBlockKey, value)
|
||||
err = setUint32WithKey(p.ContractID, ic.DAO, maxTransactionsPerBlockKey, value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -319,7 +317,7 @@ func (p *Policy) setMaxBlockSize(ic *interop.Context, args []stackitem.Item) sta
|
|||
if value > 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 {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -328,7 +326,7 @@ func (p *Policy) setMaxBlockSize(ic *interop.Context, args []stackitem.Item) sta
|
|||
}
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
err = p.setUint32WithKey(ic.DAO, maxBlockSizeKey, value)
|
||||
err = setUint32WithKey(p.ContractID, ic.DAO, maxBlockSizeKey, value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -342,7 +340,7 @@ func (p *Policy) setFeePerByte(ic *interop.Context, args []stackitem.Item) stack
|
|||
if value < 0 || value > 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 {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -351,7 +349,7 @@ func (p *Policy) setFeePerByte(ic *interop.Context, args []stackitem.Item) stack
|
|||
}
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
err = p.setInt64WithKey(ic.DAO, feePerByteKey, value)
|
||||
err = setInt64WithKey(p.ContractID, ic.DAO, feePerByteKey, value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -365,7 +363,7 @@ func (p *Policy) setMaxBlockSystemFee(ic *interop.Context, args []stackitem.Item
|
|||
if value <= minBlockSystemFee {
|
||||
panic(fmt.Errorf("MaxBlockSystemFee cannot be less then %d", minBlockSystemFee))
|
||||
}
|
||||
ok, err := p.checkValidators(ic)
|
||||
ok, err := checkValidators(ic)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -374,7 +372,7 @@ func (p *Policy) setMaxBlockSystemFee(ic *interop.Context, args []stackitem.Item
|
|||
}
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
err = p.setInt64WithKey(ic.DAO, maxBlockSystemFeeKey, value)
|
||||
err = setInt64WithKey(p.ContractID, ic.DAO, maxBlockSystemFeeKey, value)
|
||||
if err != nil {
|
||||
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
|
||||
// of blocked accounts.
|
||||
func (p *Policy) blockAccount(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
ok, err := p.checkValidators(ic)
|
||||
ok, err := checkValidators(ic)
|
||||
if err != nil {
|
||||
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
|
||||
// the list of blocked accounts.
|
||||
func (p *Policy) unblockAccount(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
ok, err := p.checkValidators(ic)
|
||||
ok, err := checkValidators(ic)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -434,46 +432,6 @@ func (p *Policy) unblockAccount(ic *interop.Context, args []stackitem.Item) stac
|
|||
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
|
||||
// like not being signed by blocked account or not exceeding block-level system
|
||||
// fee limit.
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
package native
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"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/storage"
|
||||
"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(),
|
||||
})
|
||||
}
|
||||
|
||||
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 (
|
||||
"math"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"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/transaction"
|
||||
"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/smartcontract/trigger"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
|
@ -319,3 +321,48 @@ func TestNotaryNodesReward(t *testing.T) {
|
|||
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/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -45,6 +46,13 @@ func TestMaxTransactionsPerBlock(t *testing.T) {
|
|||
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, policyHash, "setMaxTransactionsPerBlock", bigint.ToBytes(big.NewInt(1024)))
|
||||
checkResult(t, invokeRes, stackitem.NewBool(false))
|
||||
})
|
||||
}
|
||||
|
||||
func TestMaxBlockSize(t *testing.T) {
|
||||
|
@ -80,6 +88,13 @@ func TestMaxBlockSize(t *testing.T) {
|
|||
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, policyHash, "setMaxBlockSize", bigint.ToBytes(big.NewInt(102400)))
|
||||
checkResult(t, invokeRes, stackitem.NewBool(false))
|
||||
})
|
||||
}
|
||||
|
||||
func TestFeePerByte(t *testing.T) {
|
||||
|
@ -119,6 +134,13 @@ func TestFeePerByte(t *testing.T) {
|
|||
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, policyHash, "setFeePerByte", bigint.ToBytes(big.NewInt(1024)))
|
||||
checkResult(t, invokeRes, stackitem.NewBool(false))
|
||||
})
|
||||
}
|
||||
|
||||
func TestBlockSystemFee(t *testing.T) {
|
||||
|
@ -154,6 +176,13 @@ func TestBlockSystemFee(t *testing.T) {
|
|||
checkResult(t, res, stackitem.NewBigInteger(big.NewInt(100000000)))
|
||||
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) {
|
||||
|
@ -217,4 +246,11 @@ func TestBlockedAccounts(t *testing.T) {
|
|||
checkResult(t, res, stackitem.NewBool(false))
|
||||
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/netmode"
|
||||
"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/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
|
@ -31,21 +32,30 @@ import (
|
|||
type testChain struct {
|
||||
config.ProtocolConfiguration
|
||||
*mempool.Pool
|
||||
blocksCh []chan<- *block.Block
|
||||
blockheight uint32
|
||||
poolTx func(*transaction.Transaction) error
|
||||
blocks map[util.Uint256]*block.Block
|
||||
hdrHashes map[uint32]util.Uint256
|
||||
txs map[util.Uint256]*transaction.Transaction
|
||||
blocksCh []chan<- *block.Block
|
||||
blockheight uint32
|
||||
poolTx func(*transaction.Transaction) error
|
||||
poolTxWithData func(*transaction.Transaction, interface{}, *mempool.Pool) error
|
||||
blocks map[util.Uint256]*block.Block
|
||||
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 {
|
||||
return &testChain{
|
||||
Pool: mempool.New(10),
|
||||
poolTx: func(*transaction.Transaction) error { return nil },
|
||||
blocks: make(map[util.Uint256]*block.Block),
|
||||
hdrHashes: make(map[uint32]util.Uint256),
|
||||
txs: make(map[util.Uint256]*transaction.Transaction),
|
||||
Pool: mempool.New(10, 0),
|
||||
poolTx: func(*transaction.Transaction) error { return nil },
|
||||
poolTxWithData: func(*transaction.Transaction, interface{}, *mempool.Pool) error { return nil },
|
||||
blocks: make(map[util.Uint256]*block.Block),
|
||||
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 {
|
||||
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 {
|
||||
return chain.ProtocolConfiguration
|
||||
}
|
||||
|
@ -77,7 +129,7 @@ func (chain *testChain) FeePerByte() int64 {
|
|||
}
|
||||
|
||||
func (chain *testChain) P2PSigExtensionsEnabled() bool {
|
||||
return false
|
||||
return true
|
||||
}
|
||||
|
||||
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 {
|
||||
if chain.utilityTokenBalance != nil {
|
||||
return chain.utilityTokenBalance
|
||||
}
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
|
@ -230,7 +285,10 @@ func (chain *testChain) SubscribeForTransactions(ch chan<- *transaction.Transact
|
|||
func (chain *testChain) VerifyTx(*transaction.Transaction) error {
|
||||
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")
|
||||
}
|
||||
|
||||
|
|
|
@ -64,18 +64,19 @@ const (
|
|||
CMDPong CommandType = 0x19
|
||||
|
||||
// synchronization
|
||||
CMDGetHeaders CommandType = 0x20
|
||||
CMDHeaders CommandType = 0x21
|
||||
CMDGetBlocks CommandType = 0x24
|
||||
CMDMempool CommandType = 0x25
|
||||
CMDInv CommandType = 0x27
|
||||
CMDGetData CommandType = 0x28
|
||||
CMDGetBlockByIndex CommandType = 0x29
|
||||
CMDNotFound CommandType = 0x2a
|
||||
CMDTX = CommandType(payload.TXType)
|
||||
CMDBlock = CommandType(payload.BlockType)
|
||||
CMDConsensus = CommandType(payload.ConsensusType)
|
||||
CMDReject CommandType = 0x2f
|
||||
CMDGetHeaders CommandType = 0x20
|
||||
CMDHeaders CommandType = 0x21
|
||||
CMDGetBlocks CommandType = 0x24
|
||||
CMDMempool CommandType = 0x25
|
||||
CMDInv CommandType = 0x27
|
||||
CMDGetData CommandType = 0x28
|
||||
CMDGetBlockByIndex CommandType = 0x29
|
||||
CMDNotFound CommandType = 0x2a
|
||||
CMDTX = CommandType(payload.TXType)
|
||||
CMDBlock = CommandType(payload.BlockType)
|
||||
CMDConsensus = CommandType(payload.ConsensusType)
|
||||
CMDP2PNotaryRequest = CommandType(payload.P2PNotaryRequestType)
|
||||
CMDReject CommandType = 0x2f
|
||||
|
||||
// SPV protocol
|
||||
CMDFilterLoad CommandType = 0x30
|
||||
|
@ -148,6 +149,8 @@ func (m *Message) decodePayload() error {
|
|||
p = block.New(m.Network, m.StateRootInHeader)
|
||||
case CMDConsensus:
|
||||
p = consensus.NewPayload(m.Network, m.StateRootInHeader)
|
||||
case CMDP2PNotaryRequest:
|
||||
p = &payload.P2PNotaryRequest{Network: m.Network}
|
||||
case CMDGetBlocks:
|
||||
p = &payload.GetBlocks{}
|
||||
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"
|
||||
case ConsensusType:
|
||||
return "consensus"
|
||||
case P2PNotaryRequestType:
|
||||
return "p2pNotaryRequest"
|
||||
default:
|
||||
return "unknown inventory type"
|
||||
}
|
||||
}
|
||||
|
||||
// Valid returns true if the inventory (type) is known.
|
||||
func (i InventoryType) Valid() bool {
|
||||
return i == BlockType || i == TXType || i == ConsensusType
|
||||
func (i InventoryType) Valid(p2pSigExtensionsEnabled bool) bool {
|
||||
return i == BlockType || i == TXType || i == ConsensusType || (p2pSigExtensionsEnabled && i == P2PNotaryRequestType)
|
||||
}
|
||||
|
||||
// List of valid InventoryTypes.
|
||||
const (
|
||||
TXType InventoryType = 0x2b
|
||||
BlockType InventoryType = 0x2c
|
||||
ConsensusType InventoryType = 0x2d
|
||||
TXType InventoryType = 0x2b
|
||||
BlockType InventoryType = 0x2c
|
||||
ConsensusType InventoryType = 0x2d
|
||||
P2PNotaryRequestType InventoryType = 0x50
|
||||
)
|
||||
|
||||
// Inventory payload.
|
||||
|
|
|
@ -31,15 +31,22 @@ func TestEmptyInv(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestValid(t *testing.T) {
|
||||
require.True(t, TXType.Valid())
|
||||
require.True(t, BlockType.Valid())
|
||||
require.True(t, ConsensusType.Valid())
|
||||
require.False(t, InventoryType(0xFF).Valid())
|
||||
require.True(t, TXType.Valid(false))
|
||||
require.True(t, TXType.Valid(true))
|
||||
require.True(t, BlockType.Valid(false))
|
||||
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) {
|
||||
require.Equal(t, "TX", TXType.String())
|
||||
require.Equal(t, "block", BlockType.String())
|
||||
require.Equal(t, "consensus", ConsensusType.String())
|
||||
require.Equal(t, "p2pNotaryRequest", P2PNotaryRequestType.String())
|
||||
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/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/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/network/capability"
|
||||
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
||||
|
@ -59,11 +60,13 @@ type (
|
|||
// stateRootInHeader specifies if block header contain state root.
|
||||
stateRootInHeader bool
|
||||
|
||||
transport Transporter
|
||||
discovery Discoverer
|
||||
chain blockchainer.Blockchainer
|
||||
bQueue *blockQueue
|
||||
consensus consensus.Service
|
||||
transport Transporter
|
||||
discovery Discoverer
|
||||
chain blockchainer.Blockchainer
|
||||
bQueue *blockQueue
|
||||
consensus consensus.Service
|
||||
notaryRequestPool *mempool.Pool
|
||||
NotaryFeer NotaryFeer
|
||||
|
||||
lock sync.RWMutex
|
||||
peers map[Peer]bool
|
||||
|
@ -124,6 +127,15 @@ func newServerFromConstructors(config ServerConfig, chain blockchainer.Blockchai
|
|||
log: log,
|
||||
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) {
|
||||
if !s.consensusStarted.Load() {
|
||||
s.tryStartConsensus()
|
||||
|
@ -188,7 +200,7 @@ func (s *Server) Start(errChan chan error) {
|
|||
zap.Uint32("headerHeight", s.chain.HeaderHeight()))
|
||||
|
||||
s.tryStartConsensus()
|
||||
s.initStaleTxMemPool()
|
||||
s.initStaleMemPools()
|
||||
|
||||
go s.broadcastTxLoop()
|
||||
go s.relayBlocksLoop()
|
||||
|
@ -507,6 +519,9 @@ func (s *Server) handleInvCmd(p Peer, inv *payload.Inventory) error {
|
|||
cp := s.consensus.GetPayload(h)
|
||||
return cp != nil
|
||||
},
|
||||
payload.P2PNotaryRequestType: func(h util.Uint256) bool {
|
||||
return s.notaryRequestPool.ContainsKey(h)
|
||||
},
|
||||
}
|
||||
if exists := typExists[inv.Type]; exists != nil {
|
||||
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 {
|
||||
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 {
|
||||
pkt, err := msg.Bytes()
|
||||
|
@ -687,11 +708,62 @@ func (s *Server) handleTxCmd(tx *transaction.Transaction) error {
|
|||
// in the pool.
|
||||
if s.verifyAndPoolTX(tx) == RelaySucceed {
|
||||
s.consensus.OnTransaction(tx)
|
||||
s.broadcastTX(tx)
|
||||
s.broadcastTX(tx, 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.
|
||||
func (s *Server) handleAddrCmd(p Peer, addrs *payload.AddressList) error {
|
||||
if !p.CanProcessAddr() {
|
||||
|
@ -770,7 +842,7 @@ func (s *Server) handleMessage(peer Peer, msg *Message) error {
|
|||
|
||||
if peer.Handshaked() {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -808,6 +880,9 @@ func (s *Server) handleMessage(peer Peer, msg *Message) error {
|
|||
case CMDTX:
|
||||
tx := msg.Payload.(*transaction.Transaction)
|
||||
return s.handleTxCmd(tx)
|
||||
case CMDP2PNotaryRequest:
|
||||
r := msg.Payload.(*payload.P2PNotaryRequest)
|
||||
return s.handleP2PNotaryRequestCmd(r)
|
||||
case CMDPing:
|
||||
ping := msg.Payload.(*payload.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 {
|
||||
ret := s.verifyAndPoolTX(t)
|
||||
if ret == RelaySucceed {
|
||||
s.broadcastTX(t)
|
||||
s.broadcastTX(t, nil)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// broadcastTX broadcasts an inventory message about new transaction.
|
||||
func (s *Server) broadcastTX(t *transaction.Transaction) {
|
||||
func (s *Server) broadcastTX(t *transaction.Transaction, _ interface{}) {
|
||||
select {
|
||||
case s.transactions <- t:
|
||||
case <-s.quit:
|
||||
|
@ -964,8 +1039,8 @@ func (s *Server) broadcastTxHashes(hs []util.Uint256) {
|
|||
s.iteratePeersWithSendMsg(msg, Peer.EnqueuePacket, Peer.IsFullNode)
|
||||
}
|
||||
|
||||
// initStaleTxMemPool initializes mempool for stale tx processing.
|
||||
func (s *Server) initStaleTxMemPool() {
|
||||
// initStaleMemPools initializes mempools for stale tx/payload processing.
|
||||
func (s *Server) initStaleMemPools() {
|
||||
cfg := s.chain.GetConfig()
|
||||
threshold := 5
|
||||
if cfg.ValidatorsCount*2 > threshold {
|
||||
|
@ -974,6 +1049,9 @@ func (s *Server) initStaleTxMemPool() {
|
|||
|
||||
mp := s.chain.GetMemPool()
|
||||
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
|
||||
|
|
|
@ -19,6 +19,8 @@ import (
|
|||
"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/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/atomic"
|
||||
|
@ -445,7 +447,7 @@ func (s *Server) testHandleGetData(t *testing.T, invType payload.InventoryType,
|
|||
p.handshaked = true
|
||||
p.messageHandler = func(t *testing.T, msg *Message) {
|
||||
switch msg.Command {
|
||||
case CMDTX, CMDBlock, CMDConsensus:
|
||||
case CMDTX, CMDBlock, CMDConsensus, CMDP2PNotaryRequest:
|
||||
require.Equal(t, found, msg.Payload)
|
||||
recvResponse.Store(true)
|
||||
case CMDNotFound:
|
||||
|
@ -463,6 +465,7 @@ func (s *Server) testHandleGetData(t *testing.T, invType payload.InventoryType,
|
|||
func TestGetData(t *testing.T) {
|
||||
s, shutdown := startTestServer(t)
|
||||
defer shutdown()
|
||||
s.chain.(*testChain).utilityTokenBalance = big.NewInt(1000000)
|
||||
|
||||
t.Run("block", func(t *testing.T) {
|
||||
b := newDummyBlock(2, 0)
|
||||
|
@ -478,6 +481,44 @@ func TestGetData(t *testing.T) {
|
|||
notFound := []util.Uint256{hs[0], hs[2]}
|
||||
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) {
|
||||
|
@ -602,6 +643,7 @@ func TestGetHeaders(t *testing.T) {
|
|||
func TestInv(t *testing.T) {
|
||||
s, shutdown := startTestServer(t)
|
||||
defer shutdown()
|
||||
s.chain.(*testChain).utilityTokenBalance = big.NewInt(10000000)
|
||||
|
||||
var actual []util.Uint256
|
||||
p := newLocalPeer(t, s)
|
||||
|
@ -632,6 +674,24 @@ func TestInv(t *testing.T) {
|
|||
})
|
||||
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) {
|
||||
|
@ -763,3 +823,43 @@ func TestMemPool(t *testing.T) {
|
|||
s.testHandleMessage(t, p, CMDMempool, payload.NullPayload{})
|
||||
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