forked from TrueCloudLab/neoneo-go
Merge pull request #1658 from nspcc-dev/signature_collection/module
core, network: add Notary module
This commit is contained in:
commit
641896b2fb
26 changed files with 2429 additions and 372 deletions
438
internal/fakechain/fakechain.go
Normal file
438
internal/fakechain/fakechain.go
Normal file
|
@ -0,0 +1,438 @@
|
||||||
|
package fakechain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"math/big"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"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/blockchainer/services"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/mempool"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||||
|
"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/crypto"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FakeChain implements Blockchainer interface, but does not provide real functionality.
|
||||||
|
type FakeChain struct {
|
||||||
|
config.ProtocolConfiguration
|
||||||
|
*mempool.Pool
|
||||||
|
blocksCh []chan<- *block.Block
|
||||||
|
Blockheight uint32
|
||||||
|
PoolTxF 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFakeChain returns new FakeChain structure.
|
||||||
|
func NewFakeChain() *FakeChain {
|
||||||
|
return &FakeChain{
|
||||||
|
Pool: mempool.New(10, 0, false),
|
||||||
|
PoolTxF: 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},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutBlock implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) PutBlock(b *block.Block) {
|
||||||
|
chain.blocks[b.Hash()] = b
|
||||||
|
chain.hdrHashes[b.Index] = b.Hash()
|
||||||
|
atomic.StoreUint32(&chain.Blockheight, b.Index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutHeader implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) PutHeader(b *block.Block) {
|
||||||
|
chain.hdrHashes[b.Index] = b.Hash()
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutTx implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) PutTx(tx *transaction.Transaction) {
|
||||||
|
chain.txs[tx.Hash()] = tx
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyPolicyToTxSet implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) ApplyPolicyToTxSet([]*transaction.Transaction) []*transaction.Transaction {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTxStillRelevant implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) IsTxStillRelevant(t *transaction.Transaction, txpool *mempool.Pool, isPartialTx bool) bool {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExtensibleAllowed implements Blockchainer interface.
|
||||||
|
func (*FakeChain) IsExtensibleAllowed(uint160 util.Uint160) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNotaryDepositExpiration implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) GetNotaryDepositExpiration(acc util.Uint160) uint32 {
|
||||||
|
if chain.NotaryDepositExpiration != 0 {
|
||||||
|
return chain.NotaryDepositExpiration
|
||||||
|
}
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNotaryContractScriptHash implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) GetNotaryContractScriptHash() util.Uint160 {
|
||||||
|
if !chain.NotaryContractScriptHash.Equals(util.Uint160{}) {
|
||||||
|
return chain.NotaryContractScriptHash
|
||||||
|
}
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNotaryBalance implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) GetNotaryBalance(acc util.Uint160) *big.Int {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPolicer implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) GetPolicer() blockchainer.Policer {
|
||||||
|
return chain
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBaseExecFee implements Policer interface.
|
||||||
|
func (chain *FakeChain) GetBaseExecFee() int64 {
|
||||||
|
return interop.DefaultBaseExecFee
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStoragePrice implements Policer interface.
|
||||||
|
func (chain *FakeChain) GetStoragePrice() int64 {
|
||||||
|
return native.StoragePrice
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMaxVerificationGAS implements Policer interface.
|
||||||
|
func (chain *FakeChain) GetMaxVerificationGAS() int64 {
|
||||||
|
if chain.MaxVerificationGAS != 0 {
|
||||||
|
return chain.MaxVerificationGAS
|
||||||
|
}
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// PoolTxWithData implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterPostBlock implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) RegisterPostBlock(f func(blockchainer.Blockchainer, *mempool.Pool, *block.Block)) {
|
||||||
|
chain.PostBlock = append(chain.PostBlock, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfig implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) GetConfig() config.ProtocolConfiguration {
|
||||||
|
return chain.ProtocolConfiguration
|
||||||
|
}
|
||||||
|
|
||||||
|
// CalculateClaimable implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) CalculateClaimable(util.Uint160, uint32) (*big.Int, error) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// FeePerByte implements Feer interface.
|
||||||
|
func (chain *FakeChain) FeePerByte() int64 {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// P2PSigExtensionsEnabled implements Feer interface.
|
||||||
|
func (chain *FakeChain) P2PSigExtensionsEnabled() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMaxBlockSystemFee implements Policer interface.
|
||||||
|
func (chain *FakeChain) GetMaxBlockSystemFee() int64 {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMaxBlockSize implements Policer interface.
|
||||||
|
func (chain *FakeChain) GetMaxBlockSize() uint32 {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddHeaders implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) AddHeaders(...*block.Header) error {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddBlock implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) AddBlock(block *block.Block) error {
|
||||||
|
if block.Index == atomic.LoadUint32(&chain.Blockheight)+1 {
|
||||||
|
chain.PutBlock(block)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddStateRoot implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) AddStateRoot(r *state.MPTRoot) error {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockHeight implements Feer interface.
|
||||||
|
func (chain *FakeChain) BlockHeight() uint32 {
|
||||||
|
return atomic.LoadUint32(&chain.Blockheight)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) Close() {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeaderHeight implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) HeaderHeight() uint32 {
|
||||||
|
return atomic.LoadUint32(&chain.Blockheight)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAppExecResults implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) GetAppExecResults(hash util.Uint256, trig trigger.Type) ([]state.AppExecResult, error) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlock implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) GetBlock(hash util.Uint256) (*block.Block, error) {
|
||||||
|
if b, ok := chain.blocks[hash]; ok {
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCommittee implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) GetCommittee() (keys.PublicKeys, error) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetContractState implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) GetContractState(hash util.Uint160) *state.Contract {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetContractScriptHash implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) GetContractScriptHash(id int32) (util.Uint160, error) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNativeContractScriptHash implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) GetNativeContractScriptHash(name string) (util.Uint160, error) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHeaderHash implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) GetHeaderHash(n int) util.Uint256 {
|
||||||
|
return chain.hdrHashes[uint32(n)]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHeader implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) GetHeader(hash util.Uint256) (*block.Header, error) {
|
||||||
|
b, err := chain.GetBlock(hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b.Header(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNextBlockValidators implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) GetNextBlockValidators() ([]*keys.PublicKey, error) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForEachNEP17Transfer implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) ForEachNEP17Transfer(util.Uint160, func(*state.NEP17Transfer) (bool, error)) error {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNEP17Balances implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) GetNEP17Balances(util.Uint160) *state.NEP17Balances {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValidators implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) GetValidators() ([]*keys.PublicKey, error) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStandByCommittee implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) GetStandByCommittee() keys.PublicKeys {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStandByValidators implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) GetStandByValidators() keys.PublicKeys {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEnrollments implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) GetEnrollments() ([]state.Validator, error) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStateProof implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) GetStateProof(util.Uint256, []byte) ([][]byte, error) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStateRoot implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) GetStateRoot(height uint32) (*state.MPTRootState, error) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStorageItem implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) GetStorageItem(id int32, key []byte) *state.StorageItem {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTestVM implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) GetTestVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) *vm.VM {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStorageItems implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) GetStorageItems(id int32) (map[string]*state.StorageItem, error) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentHeaderHash implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) CurrentHeaderHash() util.Uint256 {
|
||||||
|
return util.Uint256{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentBlockHash implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) CurrentBlockHash() util.Uint256 {
|
||||||
|
return util.Uint256{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasBlock implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) HasBlock(h util.Uint256) bool {
|
||||||
|
_, ok := chain.blocks[h]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasTransaction implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) HasTransaction(h util.Uint256) bool {
|
||||||
|
_, ok := chain.txs[h]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransaction implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) GetTransaction(h util.Uint256) (*transaction.Transaction, uint32, error) {
|
||||||
|
if tx, ok := chain.txs[h]; ok {
|
||||||
|
return tx, 1, nil
|
||||||
|
}
|
||||||
|
return nil, 0, errors.New("not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMemPool implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) GetMemPool() *mempool.Pool {
|
||||||
|
return chain.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGoverningTokenBalance implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) GetGoverningTokenBalance(acc util.Uint160) (*big.Int, uint32) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUtilityTokenBalance implements Feer interface.
|
||||||
|
func (chain *FakeChain) GetUtilityTokenBalance(uint160 util.Uint160) *big.Int {
|
||||||
|
if chain.UtilityTokenBalance != nil {
|
||||||
|
return chain.UtilityTokenBalance
|
||||||
|
}
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ManagementContractHash implements Blockchainer interface.
|
||||||
|
func (chain FakeChain) ManagementContractHash() util.Uint160 {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// PoolTx implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) PoolTx(tx *transaction.Transaction, _ ...*mempool.Pool) error {
|
||||||
|
return chain.PoolTxF(tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOracle implements Blockchainer interface.
|
||||||
|
func (chain FakeChain) SetOracle(services.Oracle) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNotary implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) SetNotary(notary services.Notary) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubscribeForBlocks implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) SubscribeForBlocks(ch chan<- *block.Block) {
|
||||||
|
chain.blocksCh = append(chain.blocksCh, ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubscribeForExecutions implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) SubscribeForExecutions(ch chan<- *state.AppExecResult) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubscribeForNotifications implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) SubscribeForNotifications(ch chan<- *state.NotificationEvent) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubscribeForTransactions implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) SubscribeForTransactions(ch chan<- *transaction.Transaction) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyTx implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) VerifyTx(*transaction.Transaction) error {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyWitness implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) VerifyWitness(util.Uint160, crypto.Verifiable, *transaction.Witness, int64) error {
|
||||||
|
if chain.VerifyWitnessF != nil {
|
||||||
|
return chain.VerifyWitnessF()
|
||||||
|
}
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsubscribeFromBlocks implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) UnsubscribeFromBlocks(ch chan<- *block.Block) {
|
||||||
|
for i, c := range chain.blocksCh {
|
||||||
|
if c == ch {
|
||||||
|
if i < len(chain.blocksCh) {
|
||||||
|
copy(chain.blocksCh[i:], chain.blocksCh[i+1:])
|
||||||
|
}
|
||||||
|
chain.blocksCh = chain.blocksCh[:len(chain.blocksCh)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsubscribeFromExecutions implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) UnsubscribeFromExecutions(ch chan<- *state.AppExecResult) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsubscribeFromNotifications implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) UnsubscribeFromNotifications(ch chan<- *state.NotificationEvent) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsubscribeFromTransactions implements Blockchainer interface.
|
||||||
|
func (chain *FakeChain) UnsubscribeFromTransactions(ch chan<- *transaction.Transaction) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
7
pkg/config/notary_config.go
Normal file
7
pkg/config/notary_config.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
// P2PNotary stores configuration for Notary node service.
|
||||||
|
type P2PNotary struct {
|
||||||
|
Enabled bool `yaml:"Enabled"`
|
||||||
|
UnlockWallet Wallet `yaml:"UnlockWallet"`
|
||||||
|
}
|
|
@ -20,6 +20,8 @@ type (
|
||||||
RemoveUntraceableBlocks bool `yaml:"RemoveUntraceableBlocks"`
|
RemoveUntraceableBlocks bool `yaml:"RemoveUntraceableBlocks"`
|
||||||
// MaxTraceableBlocks is the length of the chain accessible to smart contracts.
|
// MaxTraceableBlocks is the length of the chain accessible to smart contracts.
|
||||||
MaxTraceableBlocks uint32 `yaml:"MaxTraceableBlocks"`
|
MaxTraceableBlocks uint32 `yaml:"MaxTraceableBlocks"`
|
||||||
|
// P2PNotary stores configuration for P2P notary node service
|
||||||
|
P2PNotary P2PNotary `yaml:"P2PNotary"`
|
||||||
// P2PSigExtensions enables additional signature-related logic.
|
// P2PSigExtensions enables additional signature-related logic.
|
||||||
P2PSigExtensions bool `yaml:"P2PSigExtensions"`
|
P2PSigExtensions bool `yaml:"P2PSigExtensions"`
|
||||||
// ReservedAttributes allows to have reserved attributes range for experimental or private purposes.
|
// ReservedAttributes allows to have reserved attributes range for experimental or private purposes.
|
||||||
|
|
|
@ -451,7 +451,7 @@ func (s *service) verifyBlock(b block.Block) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
var fee int64
|
var fee int64
|
||||||
var pool = mempool.New(len(coreb.Transactions), 0)
|
var pool = mempool.New(len(coreb.Transactions), 0, false)
|
||||||
var mainPool = s.Chain.GetMemPool()
|
var mainPool = s.Chain.GetMemPool()
|
||||||
for _, tx := range coreb.Transactions {
|
for _, tx := range coreb.Transactions {
|
||||||
var err error
|
var err error
|
||||||
|
|
|
@ -177,7 +177,7 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration, log *zap.L
|
||||||
dao: dao.NewSimple(s, cfg.Magic, cfg.StateRootInHeader),
|
dao: dao.NewSimple(s, cfg.Magic, cfg.StateRootInHeader),
|
||||||
stopCh: make(chan struct{}),
|
stopCh: make(chan struct{}),
|
||||||
runToExitCh: make(chan struct{}),
|
runToExitCh: make(chan struct{}),
|
||||||
memPool: mempool.New(cfg.MemPoolSize, 0),
|
memPool: mempool.New(cfg.MemPoolSize, 0, false),
|
||||||
sbCommittee: committee,
|
sbCommittee: committee,
|
||||||
log: log,
|
log: log,
|
||||||
events: make(chan bcEvent),
|
events: make(chan bcEvent),
|
||||||
|
@ -201,6 +201,12 @@ func (bc *Blockchain) SetOracle(mod services.Oracle) {
|
||||||
bc.contracts.Designate.OracleService.Store(mod)
|
bc.contracts.Designate.OracleService.Store(mod)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetNotary sets notary module. It doesn't protected by mutex and
|
||||||
|
// must be called before `bc.Run()` to avoid data race.
|
||||||
|
func (bc *Blockchain) SetNotary(mod services.Notary) {
|
||||||
|
bc.contracts.Designate.NotaryService.Store(mod)
|
||||||
|
}
|
||||||
|
|
||||||
func (bc *Blockchain) init() error {
|
func (bc *Blockchain) init() error {
|
||||||
// If we could not find the version in the Store, we know that there is nothing stored.
|
// If we could not find the version in the Store, we know that there is nothing stored.
|
||||||
ver, err := bc.dao.GetVersion()
|
ver, err := bc.dao.GetVersion()
|
||||||
|
@ -477,7 +483,7 @@ func (bc *Blockchain) AddBlock(block *block.Block) error {
|
||||||
if !block.MerkleRoot.Equals(merkle) {
|
if !block.MerkleRoot.Equals(merkle) {
|
||||||
return errors.New("invalid block: MerkleRoot mismatch")
|
return errors.New("invalid block: MerkleRoot mismatch")
|
||||||
}
|
}
|
||||||
mp = mempool.New(len(block.Transactions), 0)
|
mp = mempool.New(len(block.Transactions), 0, false)
|
||||||
for _, tx := range block.Transactions {
|
for _, tx := range block.Transactions {
|
||||||
var err error
|
var err error
|
||||||
// Transactions are verified before adding them
|
// Transactions are verified before adding them
|
||||||
|
@ -1637,7 +1643,7 @@ func (bc *Blockchain) verifyStateRootWitness(r *state.MPTRoot) error {
|
||||||
// current blockchain state. Note that this verification is completely isolated
|
// current blockchain state. Note that this verification is completely isolated
|
||||||
// from the main node's mempool.
|
// from the main node's mempool.
|
||||||
func (bc *Blockchain) VerifyTx(t *transaction.Transaction) error {
|
func (bc *Blockchain) VerifyTx(t *transaction.Transaction) error {
|
||||||
var mp = mempool.New(1, 0)
|
var mp = mempool.New(1, 0, false)
|
||||||
bc.lock.RLock()
|
bc.lock.RLock()
|
||||||
defer bc.lock.RUnlock()
|
defer bc.lock.RUnlock()
|
||||||
return bc.verifyAndPoolTx(t, mp, bc)
|
return bc.verifyAndPoolTx(t, mp, bc)
|
||||||
|
|
|
@ -449,7 +449,7 @@ func TestVerifyTx(t *testing.T) {
|
||||||
require.True(t, errors.Is(err, ErrAlreadyExists))
|
require.True(t, errors.Is(err, ErrAlreadyExists))
|
||||||
})
|
})
|
||||||
t.Run("MemPoolOOM", func(t *testing.T) {
|
t.Run("MemPoolOOM", func(t *testing.T) {
|
||||||
bc.memPool = mempool.New(1, 0)
|
bc.memPool = mempool.New(1, 0, false)
|
||||||
tx1 := bc.newTestTx(h, testScript)
|
tx1 := bc.newTestTx(h, testScript)
|
||||||
tx1.NetworkFee += 10000 // Give it more priority.
|
tx1.NetworkFee += 10000 // Give it more priority.
|
||||||
require.NoError(t, accs[0].SignTx(tx1))
|
require.NoError(t, accs[0].SignTx(tx1))
|
||||||
|
@ -988,7 +988,7 @@ func TestVerifyTx(t *testing.T) {
|
||||||
return tx
|
return tx
|
||||||
}
|
}
|
||||||
|
|
||||||
mp := mempool.New(10, 1)
|
mp := mempool.New(10, 1, false)
|
||||||
verificationF := func(bc blockchainer.Blockchainer, tx *transaction.Transaction, data interface{}) error {
|
verificationF := func(bc blockchainer.Blockchainer, tx *transaction.Transaction, data interface{}) error {
|
||||||
if data.(int) > 5 {
|
if data.(int) > 5 {
|
||||||
return errors.New("bad data")
|
return errors.New("bad data")
|
||||||
|
|
|
@ -65,6 +65,7 @@ type Blockchainer interface {
|
||||||
PoolTx(t *transaction.Transaction, pools ...*mempool.Pool) error
|
PoolTx(t *transaction.Transaction, pools ...*mempool.Pool) error
|
||||||
PoolTxWithData(t *transaction.Transaction, data interface{}, mp *mempool.Pool, feer mempool.Feer, verificationFunction func(bc Blockchainer, t *transaction.Transaction, data interface{}) error) error
|
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))
|
RegisterPostBlock(f func(Blockchainer, *mempool.Pool, *block.Block))
|
||||||
|
SetNotary(mod services.Notary)
|
||||||
SubscribeForBlocks(ch chan<- *block.Block)
|
SubscribeForBlocks(ch chan<- *block.Block)
|
||||||
SubscribeForExecutions(ch chan<- *state.AppExecResult)
|
SubscribeForExecutions(ch chan<- *state.AppExecResult)
|
||||||
SubscribeForNotifications(ch chan<- *state.NotificationEvent)
|
SubscribeForNotifications(ch chan<- *state.NotificationEvent)
|
||||||
|
|
8
pkg/core/blockchainer/services/notary.go
Normal file
8
pkg/core/blockchainer/services/notary.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package services
|
||||||
|
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
|
||||||
|
// Notary is a Notary module interface.
|
||||||
|
type Notary interface {
|
||||||
|
UpdateNotaryNodes(pubs keys.PublicKeys)
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/internal/testserdes"
|
"github.com/nspcc-dev/neo-go/internal/testserdes"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/chaindump"
|
"github.com/nspcc-dev/neo-go/pkg/core/chaindump"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/fee"
|
"github.com/nspcc-dev/neo-go/pkg/core/fee"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||||
|
@ -399,6 +400,9 @@ func newNEP17Transfer(sc, from, to util.Uint160, amount int64, additionalArgs ..
|
||||||
w := io.NewBufBinWriter()
|
w := io.NewBufBinWriter()
|
||||||
emit.AppCall(w.BinWriter, sc, "transfer", callflag.All, from, to, amount, additionalArgs)
|
emit.AppCall(w.BinWriter, sc, "transfer", callflag.All, from, to, amount, additionalArgs)
|
||||||
emit.Opcodes(w.BinWriter, opcode.ASSERT)
|
emit.Opcodes(w.BinWriter, opcode.ASSERT)
|
||||||
|
if w.Err != nil {
|
||||||
|
panic(fmt.Errorf("failed to create nep17 transfer transaction: %w", w.Err))
|
||||||
|
}
|
||||||
|
|
||||||
script := w.Bytes()
|
script := w.Bytes()
|
||||||
return transaction.New(testchain.Network(), script, 11000000)
|
return transaction.New(testchain.Network(), script, 11000000)
|
||||||
|
@ -555,6 +559,14 @@ func invokeContractMethodBy(t *testing.T, chain *Blockchain, signer *wallet.Acco
|
||||||
return invokeContractMethodGeneric(chain, sysfee, hash, method, signer, args...)
|
return invokeContractMethodGeneric(chain, sysfee, hash, method, signer, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func transferTokenFromMultisigAccountCheckOK(t *testing.T, chain *Blockchain, to, tokenHash util.Uint160, amount int64, additionalArgs ...interface{}) {
|
||||||
|
transferTx := transferTokenFromMultisigAccount(t, chain, to, tokenHash, amount, additionalArgs...)
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
func transferTokenFromMultisigAccount(t *testing.T, chain *Blockchain, to, tokenHash util.Uint160, amount int64, additionalArgs ...interface{}) *transaction.Transaction {
|
func transferTokenFromMultisigAccount(t *testing.T, chain *Blockchain, to, tokenHash util.Uint160, amount int64, additionalArgs ...interface{}) *transaction.Transaction {
|
||||||
transferTx := newNEP17Transfer(tokenHash, testchain.MultisigScriptHash(), to, amount, additionalArgs...)
|
transferTx := newNEP17Transfer(tokenHash, testchain.MultisigScriptHash(), to, amount, additionalArgs...)
|
||||||
transferTx.SystemFee = 100000000
|
transferTx.SystemFee = 100000000
|
||||||
|
@ -580,3 +592,19 @@ func checkBalanceOf(t *testing.T, chain *Blockchain, addr util.Uint160, expected
|
||||||
balance := chain.GetNEP17Balances(addr).Trackers[chain.contracts.GAS.ContractID]
|
balance := chain.GetNEP17Balances(addr).Trackers[chain.contracts.GAS.ContractID]
|
||||||
require.Equal(t, int64(expected), balance.Balance.Int64())
|
require.Equal(t, int64(expected), balance.Balance.Int64())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NotaryFeerStub struct {
|
||||||
|
bc blockchainer.Blockchainer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f NotaryFeerStub) FeePerByte() int64 { return f.bc.FeePerByte() }
|
||||||
|
func (f NotaryFeerStub) GetUtilityTokenBalance(acc util.Uint160) *big.Int {
|
||||||
|
return f.bc.GetNotaryBalance(acc)
|
||||||
|
}
|
||||||
|
func (f NotaryFeerStub) BlockHeight() uint32 { return f.bc.BlockHeight() }
|
||||||
|
func (f NotaryFeerStub) P2PSigExtensionsEnabled() bool { return f.bc.P2PSigExtensionsEnabled() }
|
||||||
|
func NewNotaryFeerStub(bc blockchainer.Blockchainer) NotaryFeerStub {
|
||||||
|
return NotaryFeerStub{
|
||||||
|
bc: bc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"go.uber.org/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -69,6 +70,14 @@ type Pool struct {
|
||||||
|
|
||||||
resendThreshold uint32
|
resendThreshold uint32
|
||||||
resendFunc func(*transaction.Transaction, interface{})
|
resendFunc func(*transaction.Transaction, interface{})
|
||||||
|
|
||||||
|
// subscriptions for mempool events
|
||||||
|
subscriptionsEnabled bool
|
||||||
|
subscriptionsOn atomic.Bool
|
||||||
|
stopCh chan struct{}
|
||||||
|
events chan Event
|
||||||
|
subCh chan chan<- Event // there are no other events in mempool except Event, so no need in generic subscribers type
|
||||||
|
unsubCh chan chan<- Event
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p items) Len() int { return len(p) }
|
func (p items) Len() int { return len(p) }
|
||||||
|
@ -249,6 +258,13 @@ func (mp *Pool) Add(t *transaction.Transaction, fee Feer, data ...interface{}) e
|
||||||
delete(mp.oracleResp, attrs[0].Value.(*transaction.OracleResponse).ID)
|
delete(mp.oracleResp, attrs[0].Value.(*transaction.OracleResponse).ID)
|
||||||
}
|
}
|
||||||
mp.verifiedTxes[len(mp.verifiedTxes)-1] = pItem
|
mp.verifiedTxes[len(mp.verifiedTxes)-1] = pItem
|
||||||
|
if mp.subscriptionsOn.Load() {
|
||||||
|
mp.events <- Event{
|
||||||
|
Type: TransactionRemoved,
|
||||||
|
Tx: unlucky.txn,
|
||||||
|
Data: unlucky.data,
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
mp.verifiedTxes = append(mp.verifiedTxes, pItem)
|
mp.verifiedTxes = append(mp.verifiedTxes, pItem)
|
||||||
}
|
}
|
||||||
|
@ -269,6 +285,14 @@ func (mp *Pool) Add(t *transaction.Transaction, fee Feer, data ...interface{}) e
|
||||||
|
|
||||||
updateMempoolMetrics(len(mp.verifiedTxes))
|
updateMempoolMetrics(len(mp.verifiedTxes))
|
||||||
mp.lock.Unlock()
|
mp.lock.Unlock()
|
||||||
|
|
||||||
|
if mp.subscriptionsOn.Load() {
|
||||||
|
mp.events <- Event{
|
||||||
|
Type: TransactionAdded,
|
||||||
|
Tx: pItem.txn,
|
||||||
|
Data: pItem.data,
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,6 +331,13 @@ func (mp *Pool) removeInternal(hash util.Uint256, feer Feer) {
|
||||||
if attrs := tx.GetAttributes(transaction.OracleResponseT); len(attrs) != 0 {
|
if attrs := tx.GetAttributes(transaction.OracleResponseT); len(attrs) != 0 {
|
||||||
delete(mp.oracleResp, attrs[0].Value.(*transaction.OracleResponse).ID)
|
delete(mp.oracleResp, attrs[0].Value.(*transaction.OracleResponse).ID)
|
||||||
}
|
}
|
||||||
|
if mp.subscriptionsOn.Load() {
|
||||||
|
mp.events <- Event{
|
||||||
|
Type: TransactionRemoved,
|
||||||
|
Tx: itm.txn,
|
||||||
|
Data: itm.data,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
updateMempoolMetrics(len(mp.verifiedTxes))
|
updateMempoolMetrics(len(mp.verifiedTxes))
|
||||||
}
|
}
|
||||||
|
@ -325,7 +356,9 @@ func (mp *Pool) RemoveStale(isOK func(*transaction.Transaction) bool, feer Feer)
|
||||||
mp.conflicts = make(map[util.Uint256][]util.Uint256)
|
mp.conflicts = make(map[util.Uint256][]util.Uint256)
|
||||||
}
|
}
|
||||||
height := feer.BlockHeight()
|
height := feer.BlockHeight()
|
||||||
var staleItems []item
|
var (
|
||||||
|
staleItems []item
|
||||||
|
)
|
||||||
for _, itm := range mp.verifiedTxes {
|
for _, itm := range mp.verifiedTxes {
|
||||||
if isOK(itm.txn) && mp.checkPolicy(itm.txn, policyChanged) && mp.tryAddSendersFee(itm.txn, feer, true) {
|
if isOK(itm.txn) && mp.checkPolicy(itm.txn, policyChanged) && mp.tryAddSendersFee(itm.txn, feer, true) {
|
||||||
newVerifiedTxes = append(newVerifiedTxes, itm)
|
newVerifiedTxes = append(newVerifiedTxes, itm)
|
||||||
|
@ -348,6 +381,13 @@ func (mp *Pool) RemoveStale(isOK func(*transaction.Transaction) bool, feer Feer)
|
||||||
if attrs := itm.txn.GetAttributes(transaction.OracleResponseT); len(attrs) != 0 {
|
if attrs := itm.txn.GetAttributes(transaction.OracleResponseT); len(attrs) != 0 {
|
||||||
delete(mp.oracleResp, attrs[0].Value.(*transaction.OracleResponse).ID)
|
delete(mp.oracleResp, attrs[0].Value.(*transaction.OracleResponse).ID)
|
||||||
}
|
}
|
||||||
|
if mp.subscriptionsOn.Load() {
|
||||||
|
mp.events <- Event{
|
||||||
|
Type: TransactionRemoved,
|
||||||
|
Tx: itm.txn,
|
||||||
|
Data: itm.data,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(staleItems) != 0 {
|
if len(staleItems) != 0 {
|
||||||
|
@ -377,16 +417,23 @@ func (mp *Pool) checkPolicy(tx *transaction.Transaction, policyChanged bool) boo
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new Pool struct.
|
// New returns a new Pool struct.
|
||||||
func New(capacity int, payerIndex int) *Pool {
|
func New(capacity int, payerIndex int, enableSubscriptions bool) *Pool {
|
||||||
return &Pool{
|
mp := &Pool{
|
||||||
verifiedMap: make(map[util.Uint256]*transaction.Transaction),
|
verifiedMap: make(map[util.Uint256]*transaction.Transaction),
|
||||||
verifiedTxes: make([]item, 0, capacity),
|
verifiedTxes: make([]item, 0, capacity),
|
||||||
capacity: capacity,
|
capacity: capacity,
|
||||||
payerIndex: payerIndex,
|
payerIndex: payerIndex,
|
||||||
fees: make(map[util.Uint160]utilityBalanceAndFees),
|
fees: make(map[util.Uint160]utilityBalanceAndFees),
|
||||||
conflicts: make(map[util.Uint256][]util.Uint256),
|
conflicts: make(map[util.Uint256][]util.Uint256),
|
||||||
oracleResp: make(map[uint64]util.Uint256),
|
oracleResp: make(map[uint64]util.Uint256),
|
||||||
|
subscriptionsEnabled: enableSubscriptions,
|
||||||
|
stopCh: make(chan struct{}),
|
||||||
|
events: make(chan Event),
|
||||||
|
subCh: make(chan chan<- Event),
|
||||||
|
unsubCh: make(chan chan<- Event),
|
||||||
}
|
}
|
||||||
|
mp.subscriptionsOn.Store(false)
|
||||||
|
return mp
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetResendThreshold sets threshold after which transaction will be considered stale
|
// SetResendThreshold sets threshold after which transaction will be considered stale
|
||||||
|
|
|
@ -45,7 +45,7 @@ func (fs *FeerStub) P2PSigExtensionsEnabled() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMemPoolAddRemoveWithFeer(t *testing.T, fs Feer) {
|
func testMemPoolAddRemoveWithFeer(t *testing.T, fs Feer) {
|
||||||
mp := New(10, 0)
|
mp := New(10, 0, false)
|
||||||
tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
||||||
tx.Nonce = 0
|
tx.Nonce = 0
|
||||||
tx.Signers = []transaction.Signer{{Account: util.Uint160{1, 2, 3}}}
|
tx.Signers = []transaction.Signer{{Account: util.Uint160{1, 2, 3}}}
|
||||||
|
@ -66,7 +66,7 @@ func testMemPoolAddRemoveWithFeer(t *testing.T, fs Feer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMemPoolRemoveStale(t *testing.T) {
|
func TestMemPoolRemoveStale(t *testing.T) {
|
||||||
mp := New(5, 0)
|
mp := New(5, 0, false)
|
||||||
txs := make([]*transaction.Transaction, 5)
|
txs := make([]*transaction.Transaction, 5)
|
||||||
for i := range txs {
|
for i := range txs {
|
||||||
txs[i] = transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
txs[i] = transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
||||||
|
@ -117,7 +117,7 @@ func TestMemPoolAddRemove(t *testing.T) {
|
||||||
func TestOverCapacity(t *testing.T) {
|
func TestOverCapacity(t *testing.T) {
|
||||||
var fs = &FeerStub{balance: 10000000}
|
var fs = &FeerStub{balance: 10000000}
|
||||||
const mempoolSize = 10
|
const mempoolSize = 10
|
||||||
mp := New(mempoolSize, 0)
|
mp := New(mempoolSize, 0, false)
|
||||||
|
|
||||||
for i := 0; i < mempoolSize; i++ {
|
for i := 0; i < mempoolSize; i++ {
|
||||||
tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
||||||
|
@ -193,7 +193,7 @@ func TestOverCapacity(t *testing.T) {
|
||||||
func TestGetVerified(t *testing.T) {
|
func TestGetVerified(t *testing.T) {
|
||||||
var fs = &FeerStub{}
|
var fs = &FeerStub{}
|
||||||
const mempoolSize = 10
|
const mempoolSize = 10
|
||||||
mp := New(mempoolSize, 0)
|
mp := New(mempoolSize, 0, false)
|
||||||
|
|
||||||
txes := make([]*transaction.Transaction, 0, mempoolSize)
|
txes := make([]*transaction.Transaction, 0, mempoolSize)
|
||||||
for i := 0; i < mempoolSize; i++ {
|
for i := 0; i < mempoolSize; i++ {
|
||||||
|
@ -217,7 +217,7 @@ func TestGetVerified(t *testing.T) {
|
||||||
func TestRemoveStale(t *testing.T) {
|
func TestRemoveStale(t *testing.T) {
|
||||||
var fs = &FeerStub{}
|
var fs = &FeerStub{}
|
||||||
const mempoolSize = 10
|
const mempoolSize = 10
|
||||||
mp := New(mempoolSize, 0)
|
mp := New(mempoolSize, 0, false)
|
||||||
|
|
||||||
txes1 := make([]*transaction.Transaction, 0, mempoolSize/2)
|
txes1 := make([]*transaction.Transaction, 0, mempoolSize/2)
|
||||||
txes2 := make([]*transaction.Transaction, 0, mempoolSize/2)
|
txes2 := make([]*transaction.Transaction, 0, mempoolSize/2)
|
||||||
|
@ -250,7 +250,7 @@ func TestRemoveStale(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMemPoolFees(t *testing.T) {
|
func TestMemPoolFees(t *testing.T) {
|
||||||
mp := New(10, 0)
|
mp := New(10, 0, false)
|
||||||
fs := &FeerStub{balance: 10000000}
|
fs := &FeerStub{balance: 10000000}
|
||||||
sender0 := util.Uint160{1, 2, 3}
|
sender0 := util.Uint160{1, 2, 3}
|
||||||
tx0 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
tx0 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
||||||
|
@ -361,7 +361,7 @@ func TestMempoolItemsOrder(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMempoolAddRemoveOracleResponse(t *testing.T) {
|
func TestMempoolAddRemoveOracleResponse(t *testing.T) {
|
||||||
mp := New(3, 0)
|
mp := New(3, 0, false)
|
||||||
nonce := uint32(0)
|
nonce := uint32(0)
|
||||||
fs := &FeerStub{balance: 10000}
|
fs := &FeerStub{balance: 10000}
|
||||||
newTx := func(netFee int64, id uint64) *transaction.Transaction {
|
newTx := func(netFee int64, id uint64) *transaction.Transaction {
|
||||||
|
@ -431,7 +431,7 @@ func TestMempoolAddRemoveOracleResponse(t *testing.T) {
|
||||||
|
|
||||||
func TestMempoolAddRemoveConflicts(t *testing.T) {
|
func TestMempoolAddRemoveConflicts(t *testing.T) {
|
||||||
capacity := 6
|
capacity := 6
|
||||||
mp := New(capacity, 0)
|
mp := New(capacity, 0, false)
|
||||||
var (
|
var (
|
||||||
fs = &FeerStub{p2pSigExt: true, balance: 100000}
|
fs = &FeerStub{p2pSigExt: true, balance: 100000}
|
||||||
nonce uint32 = 1
|
nonce uint32 = 1
|
||||||
|
@ -561,7 +561,7 @@ func TestMempoolAddWithDataGetData(t *testing.T) {
|
||||||
blockHeight: 5,
|
blockHeight: 5,
|
||||||
balance: 100,
|
balance: 100,
|
||||||
}
|
}
|
||||||
mp := New(10, 1)
|
mp := New(10, 1, false)
|
||||||
newTx := func(t *testing.T, netFee int64) *transaction.Transaction {
|
newTx := func(t *testing.T, netFee int64) *transaction.Transaction {
|
||||||
tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.RET)}, 0)
|
tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.RET)}, 0)
|
||||||
tx.Signers = []transaction.Signer{{}, {}}
|
tx.Signers = []transaction.Signer{{}, {}}
|
||||||
|
|
86
pkg/core/mempool/subscriptions.go
Normal file
86
pkg/core/mempool/subscriptions.go
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
package mempool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EventType represents mempool event type.
|
||||||
|
type EventType byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
// TransactionAdded marks transaction addition mempool event.
|
||||||
|
TransactionAdded EventType = 0x01
|
||||||
|
// TransactionRemoved marks transaction removal mempool event.
|
||||||
|
TransactionRemoved EventType = 0x02
|
||||||
|
)
|
||||||
|
|
||||||
|
// Event represents one of mempool events: transaction was added or removed from mempool.
|
||||||
|
type Event struct {
|
||||||
|
Type EventType
|
||||||
|
Tx *transaction.Transaction
|
||||||
|
Data interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunSubscriptions runs subscriptions goroutine if mempool subscriptions are enabled.
|
||||||
|
// You should manually free the resources by calling StopSubscriptions on mempool shutdown.
|
||||||
|
func (mp *Pool) RunSubscriptions() {
|
||||||
|
if !mp.subscriptionsEnabled {
|
||||||
|
panic("subscriptions are disabled")
|
||||||
|
}
|
||||||
|
if !mp.subscriptionsOn.Load() {
|
||||||
|
mp.subscriptionsOn.Store(true)
|
||||||
|
go mp.notificationDispatcher()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopSubscriptions stops mempool events loop.
|
||||||
|
func (mp *Pool) StopSubscriptions() {
|
||||||
|
if !mp.subscriptionsEnabled {
|
||||||
|
panic("subscriptions are disabled")
|
||||||
|
}
|
||||||
|
if mp.subscriptionsOn.Load() {
|
||||||
|
mp.subscriptionsOn.Store(false)
|
||||||
|
close(mp.stopCh)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubscribeForTransactions adds given channel to new mempool event broadcasting, so when
|
||||||
|
// there is a new transactions added to mempool or an existing transaction removed from
|
||||||
|
// mempool you'll receive it via this channel.
|
||||||
|
func (mp *Pool) SubscribeForTransactions(ch chan<- Event) {
|
||||||
|
if mp.subscriptionsOn.Load() {
|
||||||
|
mp.subCh <- ch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsubscribeFromTransactions unsubscribes given channel from new mempool notifications,
|
||||||
|
// you can close it afterwards. Passing non-subscribed channel is a no-op.
|
||||||
|
func (mp *Pool) UnsubscribeFromTransactions(ch chan<- Event) {
|
||||||
|
if mp.subscriptionsOn.Load() {
|
||||||
|
mp.unsubCh <- ch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// notificationDispatcher manages subscription to events and broadcasts new events.
|
||||||
|
func (mp *Pool) notificationDispatcher() {
|
||||||
|
var (
|
||||||
|
// These are just sets of subscribers, though modelled as maps
|
||||||
|
// for ease of management (not a lot of subscriptions is really
|
||||||
|
// expected, but maps are convenient for adding/deleting elements).
|
||||||
|
txFeed = make(map[chan<- Event]bool)
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-mp.stopCh:
|
||||||
|
return
|
||||||
|
case sub := <-mp.subCh:
|
||||||
|
txFeed[sub] = true
|
||||||
|
case unsub := <-mp.unsubCh:
|
||||||
|
delete(txFeed, unsub)
|
||||||
|
case event := <-mp.events:
|
||||||
|
for ch := range txFeed {
|
||||||
|
ch <- event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
98
pkg/core/mempool/subscriptions_test.go
Normal file
98
pkg/core/mempool/subscriptions_test.go
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
package mempool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSubscriptions(t *testing.T) {
|
||||||
|
t.Run("disabled subscriptions", func(t *testing.T) {
|
||||||
|
mp := New(5, 0, false)
|
||||||
|
require.Panics(t, func() {
|
||||||
|
mp.RunSubscriptions()
|
||||||
|
})
|
||||||
|
require.Panics(t, func() {
|
||||||
|
mp.StopSubscriptions()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("enabled subscriptions", func(t *testing.T) {
|
||||||
|
fs := &FeerStub{balance: 100}
|
||||||
|
mp := New(2, 0, true)
|
||||||
|
mp.RunSubscriptions()
|
||||||
|
subChan1 := make(chan Event, 3)
|
||||||
|
subChan2 := make(chan Event, 3)
|
||||||
|
mp.SubscribeForTransactions(subChan1)
|
||||||
|
defer mp.StopSubscriptions()
|
||||||
|
|
||||||
|
txs := make([]*transaction.Transaction, 4)
|
||||||
|
for i := range txs {
|
||||||
|
txs[i] = transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
||||||
|
txs[i].Nonce = uint32(i)
|
||||||
|
txs[i].Signers = []transaction.Signer{{Account: util.Uint160{1, 2, 3}}}
|
||||||
|
txs[i].NetworkFee = int64(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add tx
|
||||||
|
require.NoError(t, mp.Add(txs[0], fs))
|
||||||
|
require.Eventually(t, func() bool { return len(subChan1) == 1 }, time.Second, time.Millisecond*100)
|
||||||
|
event := <-subChan1
|
||||||
|
require.Equal(t, Event{Type: TransactionAdded, Tx: txs[0]}, event)
|
||||||
|
|
||||||
|
// severak subscribers
|
||||||
|
mp.SubscribeForTransactions(subChan2)
|
||||||
|
require.NoError(t, mp.Add(txs[1], fs))
|
||||||
|
require.Eventually(t, func() bool { return len(subChan1) == 1 && len(subChan2) == 1 }, time.Second, time.Millisecond*100)
|
||||||
|
event1 := <-subChan1
|
||||||
|
event2 := <-subChan2
|
||||||
|
require.Equal(t, Event{Type: TransactionAdded, Tx: txs[1]}, event1)
|
||||||
|
require.Equal(t, Event{Type: TransactionAdded, Tx: txs[1]}, event2)
|
||||||
|
|
||||||
|
// reach capacity
|
||||||
|
require.NoError(t, mp.Add(txs[2], &FeerStub{}))
|
||||||
|
require.Eventually(t, func() bool { return len(subChan1) == 2 && len(subChan2) == 2 }, time.Second, time.Millisecond*100)
|
||||||
|
event1 = <-subChan1
|
||||||
|
event2 = <-subChan2
|
||||||
|
require.Equal(t, Event{Type: TransactionRemoved, Tx: txs[0]}, event1)
|
||||||
|
require.Equal(t, Event{Type: TransactionRemoved, Tx: txs[0]}, event2)
|
||||||
|
event1 = <-subChan1
|
||||||
|
event2 = <-subChan2
|
||||||
|
require.Equal(t, Event{Type: TransactionAdded, Tx: txs[2]}, event1)
|
||||||
|
require.Equal(t, Event{Type: TransactionAdded, Tx: txs[2]}, event2)
|
||||||
|
|
||||||
|
// remove tx
|
||||||
|
mp.Remove(txs[1].Hash(), fs)
|
||||||
|
require.Eventually(t, func() bool { return len(subChan1) == 1 && len(subChan2) == 1 }, time.Second, time.Millisecond*100)
|
||||||
|
event1 = <-subChan1
|
||||||
|
event2 = <-subChan2
|
||||||
|
require.Equal(t, Event{Type: TransactionRemoved, Tx: txs[1]}, event1)
|
||||||
|
require.Equal(t, Event{Type: TransactionRemoved, Tx: txs[1]}, event2)
|
||||||
|
|
||||||
|
// remove stale
|
||||||
|
mp.RemoveStale(func(tx *transaction.Transaction) bool {
|
||||||
|
if tx.Hash().Equals(txs[2].Hash()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}, fs)
|
||||||
|
require.Eventually(t, func() bool { return len(subChan1) == 1 && len(subChan2) == 1 }, time.Second, time.Millisecond*100)
|
||||||
|
event1 = <-subChan1
|
||||||
|
event2 = <-subChan2
|
||||||
|
require.Equal(t, Event{Type: TransactionRemoved, Tx: txs[2]}, event1)
|
||||||
|
require.Equal(t, Event{Type: TransactionRemoved, Tx: txs[2]}, event2)
|
||||||
|
|
||||||
|
// unsubscribe
|
||||||
|
mp.UnsubscribeFromTransactions(subChan1)
|
||||||
|
require.NoError(t, mp.Add(txs[3], fs))
|
||||||
|
require.Eventually(t, func() bool { return len(subChan2) == 1 }, time.Second, time.Millisecond*100)
|
||||||
|
event2 = <-subChan2
|
||||||
|
require.Equal(t, 0, len(subChan1))
|
||||||
|
require.Equal(t, Event{Type: TransactionAdded, Tx: txs[3]}, event2)
|
||||||
|
})
|
||||||
|
}
|
|
@ -37,6 +37,8 @@ type Designate struct {
|
||||||
p2pSigExtensionsEnabled bool
|
p2pSigExtensionsEnabled bool
|
||||||
|
|
||||||
OracleService atomic.Value
|
OracleService atomic.Value
|
||||||
|
// NotaryService represents Notary node module.
|
||||||
|
NotaryService atomic.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
type roleData struct {
|
type roleData struct {
|
||||||
|
@ -183,10 +185,15 @@ func (s *Designate) updateCachedRoleData(v *atomic.Value, d dao.DAO, r Role) err
|
||||||
addr: s.hashFromNodes(r, nodeKeys),
|
addr: s.hashFromNodes(r, nodeKeys),
|
||||||
height: height,
|
height: height,
|
||||||
})
|
})
|
||||||
if r == RoleOracle {
|
switch r {
|
||||||
|
case RoleOracle:
|
||||||
if orc, _ := s.OracleService.Load().(services.Oracle); orc != nil {
|
if orc, _ := s.OracleService.Load().(services.Oracle); orc != nil {
|
||||||
orc.UpdateOracleNodes(nodeKeys.Copy())
|
orc.UpdateOracleNodes(nodeKeys.Copy())
|
||||||
}
|
}
|
||||||
|
case RoleP2PNotary:
|
||||||
|
if ntr, _ := s.NotaryService.Load().(services.Notary); ntr != nil {
|
||||||
|
ntr.UpdateNotaryNodes(nodeKeys.Copy())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
647
pkg/core/notary_test.go
Normal file
647
pkg/core/notary_test.go
Normal file
|
@ -0,0 +1,647 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"path"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/internal/testchain"
|
||||||
|
"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/native"
|
||||||
|
"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/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/services/notary"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"go.uber.org/zap/zaptest"
|
||||||
|
)
|
||||||
|
|
||||||
|
const notaryModulePath = "../services/notary/"
|
||||||
|
|
||||||
|
func getTestNotary(t *testing.T, bc *Blockchain, walletPath, pass string, onTx func(tx *transaction.Transaction) error) (*wallet.Account, *notary.Notary, *mempool.Pool) {
|
||||||
|
bc.config.P2PNotary = config.P2PNotary{
|
||||||
|
Enabled: true,
|
||||||
|
UnlockWallet: config.Wallet{
|
||||||
|
Path: path.Join(notaryModulePath, walletPath),
|
||||||
|
Password: pass,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mp := mempool.New(10, 1, true)
|
||||||
|
ntr, err := notary.NewNotary(bc, mp, zaptest.NewLogger(t), onTx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
w, err := wallet.NewWalletFromFile(path.Join(notaryModulePath, walletPath))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, w.Accounts[0].Decrypt(pass))
|
||||||
|
return w.Accounts[0], ntr, mp
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNotary(t *testing.T) {
|
||||||
|
bc := newTestChain(t)
|
||||||
|
defer bc.Close()
|
||||||
|
var (
|
||||||
|
nonce uint32
|
||||||
|
nvbDiffFallback uint32 = 20
|
||||||
|
)
|
||||||
|
|
||||||
|
mtx := sync.RWMutex{}
|
||||||
|
completedTxes := make(map[util.Uint256]*transaction.Transaction)
|
||||||
|
var unluckies []*payload.P2PNotaryRequest
|
||||||
|
var (
|
||||||
|
finalizeWithError bool
|
||||||
|
choosy bool
|
||||||
|
)
|
||||||
|
onTransaction := func(tx *transaction.Transaction) error {
|
||||||
|
mtx.Lock()
|
||||||
|
defer mtx.Unlock()
|
||||||
|
if !choosy {
|
||||||
|
if completedTxes[tx.Hash()] != nil {
|
||||||
|
panic("transaction was completed twice")
|
||||||
|
}
|
||||||
|
if finalizeWithError {
|
||||||
|
return errors.New("error while finalizing transaction")
|
||||||
|
}
|
||||||
|
completedTxes[tx.Hash()] = tx
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, unl := range unluckies {
|
||||||
|
if tx.Hash().Equals(unl.FallbackTransaction.Hash()) {
|
||||||
|
return errors.New("error while finalizing transaction")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
completedTxes[tx.Hash()] = tx
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
acc1, ntr1, mp1 := getTestNotary(t, bc, "./testdata/notary1.json", "one", onTransaction)
|
||||||
|
acc2, _, _ := getTestNotary(t, bc, "./testdata/notary2.json", "two", onTransaction)
|
||||||
|
randomAcc, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
bc.SetNotary(ntr1)
|
||||||
|
bc.RegisterPostBlock(func(bc blockchainer.Blockchainer, pool *mempool.Pool, b *block.Block) {
|
||||||
|
ntr1.PostPersist(bc, pool, b)
|
||||||
|
})
|
||||||
|
|
||||||
|
notaryNodes := keys.PublicKeys{acc1.PrivateKey().PublicKey(), acc2.PrivateKey().PublicKey()}
|
||||||
|
bc.setNodesByRole(t, true, native.RoleP2PNotary, notaryNodes)
|
||||||
|
|
||||||
|
createFallbackTx := func(requester *wallet.Account, mainTx *transaction.Transaction, nvbIncrement ...uint32) *transaction.Transaction {
|
||||||
|
fallback := transaction.New(testchain.Network(), []byte{byte(opcode.RET)}, 2000_0000)
|
||||||
|
fallback.Nonce = nonce
|
||||||
|
nonce++
|
||||||
|
fallback.SystemFee = 1_0000_0000
|
||||||
|
fallback.ValidUntilBlock = bc.BlockHeight() + 50
|
||||||
|
fallback.Signers = []transaction.Signer{
|
||||||
|
{
|
||||||
|
Account: bc.GetNotaryContractScriptHash(),
|
||||||
|
Scopes: transaction.None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Account: requester.PrivateKey().PublicKey().GetScriptHash(),
|
||||||
|
Scopes: transaction.None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
nvb := bc.BlockHeight() + nvbDiffFallback
|
||||||
|
if len(nvbIncrement) != 0 {
|
||||||
|
nvb += nvbIncrement[0]
|
||||||
|
}
|
||||||
|
fallback.Attributes = []transaction.Attribute{
|
||||||
|
{
|
||||||
|
Type: transaction.NotaryAssistedT,
|
||||||
|
Value: &transaction.NotaryAssisted{NKeys: 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: transaction.NotValidBeforeT,
|
||||||
|
Value: &transaction.NotValidBefore{Height: nvb},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: transaction.ConflictsT,
|
||||||
|
Value: &transaction.Conflicts{Hash: mainTx.Hash()},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
fallback.Scripts = []transaction.Witness{
|
||||||
|
{
|
||||||
|
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, make([]byte, 64)...),
|
||||||
|
VerificationScript: []byte{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
requester.SignTx(fallback)
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
createStandardRequest := func(requesters []*wallet.Account, NVBincrements ...uint32) []*payload.P2PNotaryRequest {
|
||||||
|
mainTx := *transaction.New(testchain.Network(), []byte{byte(opcode.RET)}, 11000000)
|
||||||
|
mainTx.Nonce = nonce
|
||||||
|
nonce++
|
||||||
|
mainTx.SystemFee = 100000000
|
||||||
|
mainTx.ValidUntilBlock = bc.BlockHeight() + 2*nvbDiffFallback
|
||||||
|
signers := make([]transaction.Signer, len(requesters)+1)
|
||||||
|
for i := range requesters {
|
||||||
|
signers[i] = transaction.Signer{
|
||||||
|
Account: requesters[i].PrivateKey().PublicKey().GetScriptHash(),
|
||||||
|
Scopes: transaction.None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
signers[len(signers)-1] = transaction.Signer{
|
||||||
|
Account: bc.GetNotaryContractScriptHash(),
|
||||||
|
Scopes: transaction.None,
|
||||||
|
}
|
||||||
|
mainTx.Signers = signers
|
||||||
|
mainTx.Attributes = []transaction.Attribute{
|
||||||
|
{
|
||||||
|
Type: transaction.NotaryAssistedT,
|
||||||
|
Value: &transaction.NotaryAssisted{NKeys: uint8(len(requesters))},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
payloads := make([]*payload.P2PNotaryRequest, len(requesters))
|
||||||
|
for i := range payloads {
|
||||||
|
cp := mainTx
|
||||||
|
main := &cp
|
||||||
|
scripts := make([]transaction.Witness, len(requesters)+1)
|
||||||
|
for j := range requesters {
|
||||||
|
scripts[j].VerificationScript = requesters[j].PrivateKey().PublicKey().GetVerificationScript()
|
||||||
|
}
|
||||||
|
scripts[i].InvocationScript = append([]byte{byte(opcode.PUSHDATA1), 64}, requesters[i].PrivateKey().Sign(main.GetSignedPart())...)
|
||||||
|
main.Scripts = scripts
|
||||||
|
var fallback *transaction.Transaction
|
||||||
|
if len(NVBincrements) == len(requesters) {
|
||||||
|
fallback = createFallbackTx(requesters[i], main, NVBincrements[i])
|
||||||
|
} else {
|
||||||
|
fallback = createFallbackTx(requesters[i], main)
|
||||||
|
}
|
||||||
|
payloads[i] = &payload.P2PNotaryRequest{
|
||||||
|
MainTransaction: main,
|
||||||
|
FallbackTransaction: fallback,
|
||||||
|
Network: netmode.UnitTestNet,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return payloads
|
||||||
|
}
|
||||||
|
createMultisigRequest := func(m int, requesters []*wallet.Account) []*payload.P2PNotaryRequest {
|
||||||
|
mainTx := *transaction.New(testchain.Network(), []byte{byte(opcode.RET)}, 11000000)
|
||||||
|
mainTx.Nonce = nonce
|
||||||
|
nonce++
|
||||||
|
mainTx.SystemFee = 100000000
|
||||||
|
mainTx.ValidUntilBlock = bc.BlockHeight() + 2*nvbDiffFallback
|
||||||
|
pubs := make(keys.PublicKeys, len(requesters))
|
||||||
|
for i, r := range requesters {
|
||||||
|
pubs[i] = r.PrivateKey().PublicKey()
|
||||||
|
}
|
||||||
|
script, err := smartcontract.CreateMultiSigRedeemScript(m, pubs)
|
||||||
|
require.NoError(t, err)
|
||||||
|
mainTx.Signers = []transaction.Signer{
|
||||||
|
{
|
||||||
|
Account: hash.Hash160(script),
|
||||||
|
Scopes: transaction.None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Account: bc.GetNotaryContractScriptHash(),
|
||||||
|
Scopes: transaction.None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mainTx.Attributes = []transaction.Attribute{
|
||||||
|
{
|
||||||
|
Type: transaction.NotaryAssistedT,
|
||||||
|
Value: &transaction.NotaryAssisted{NKeys: uint8(len(requesters))},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
payloads := make([]*payload.P2PNotaryRequest, len(requesters))
|
||||||
|
// we'll collect only m signatures out of n (so only m payloads are needed), but let's create payloads for all requesters (for the next tests)
|
||||||
|
for i := range payloads {
|
||||||
|
cp := mainTx
|
||||||
|
main := &cp
|
||||||
|
main.Scripts = []transaction.Witness{
|
||||||
|
{
|
||||||
|
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, requesters[i].PrivateKey().Sign(main.GetSignedPart())...),
|
||||||
|
VerificationScript: script,
|
||||||
|
},
|
||||||
|
{}, // empty Notary witness
|
||||||
|
}
|
||||||
|
fallback := createFallbackTx(requesters[i], main)
|
||||||
|
payloads[i] = &payload.P2PNotaryRequest{
|
||||||
|
MainTransaction: main,
|
||||||
|
FallbackTransaction: fallback,
|
||||||
|
Network: netmode.UnitTestNet,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return payloads
|
||||||
|
}
|
||||||
|
checkSigTx := func(t *testing.T, requests []*payload.P2PNotaryRequest, sentCount int, shouldComplete bool) {
|
||||||
|
nKeys := len(requests)
|
||||||
|
completedTx := completedTxes[requests[0].MainTransaction.Hash()]
|
||||||
|
if sentCount == nKeys && shouldComplete {
|
||||||
|
require.NotNil(t, completedTx, errors.New("main transaction expected to be completed"))
|
||||||
|
require.Equal(t, nKeys+1, len(completedTx.Signers))
|
||||||
|
require.Equal(t, nKeys+1, len(completedTx.Scripts))
|
||||||
|
interopCtx := bc.newInteropContext(trigger.Verification, bc.dao, nil, completedTx)
|
||||||
|
for i, req := range requests {
|
||||||
|
require.Equal(t, req.MainTransaction.Scripts[i], completedTx.Scripts[i])
|
||||||
|
_, err := bc.verifyHashAgainstScript(completedTx.Signers[i].Account, &completedTx.Scripts[i], interopCtx, -1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
require.Equal(t, transaction.Witness{
|
||||||
|
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, acc1.PrivateKey().Sign(requests[0].MainTransaction.GetSignedPart())...),
|
||||||
|
VerificationScript: []byte{},
|
||||||
|
}, completedTx.Scripts[nKeys])
|
||||||
|
} else {
|
||||||
|
require.Nil(t, completedTx, fmt.Errorf("main transaction shouldn't be completed: sent %d out of %d requests", sentCount, nKeys))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkMultisigTx := func(t *testing.T, nSigs int, requests []*payload.P2PNotaryRequest, sentCount int, shouldComplete bool) {
|
||||||
|
completedTx := completedTxes[requests[0].MainTransaction.Hash()]
|
||||||
|
if sentCount >= nSigs && shouldComplete {
|
||||||
|
require.NotNil(t, completedTx, errors.New("main transaction expected to be completed"))
|
||||||
|
require.Equal(t, 2, len(completedTx.Signers))
|
||||||
|
require.Equal(t, 2, len(completedTx.Scripts))
|
||||||
|
interopCtx := bc.newInteropContext(trigger.Verification, bc.dao, nil, completedTx)
|
||||||
|
_, err := bc.verifyHashAgainstScript(completedTx.Signers[0].Account, &completedTx.Scripts[0], interopCtx, -1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, transaction.Witness{
|
||||||
|
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, acc1.PrivateKey().Sign(requests[0].MainTransaction.GetSignedPart())...),
|
||||||
|
VerificationScript: []byte{},
|
||||||
|
}, completedTx.Scripts[1])
|
||||||
|
// check that only nSigs out of nKeys signatures are presented in the invocation script
|
||||||
|
for i, req := range requests[:nSigs] {
|
||||||
|
require.True(t, bytes.Contains(completedTx.Scripts[0].InvocationScript, req.MainTransaction.Scripts[0].InvocationScript), fmt.Errorf("signature from extra request #%d shouldn't be presented in the main tx", i))
|
||||||
|
}
|
||||||
|
// the rest (nKeys-nSigs) out of nKeys shouldn't be presented in the invocation script
|
||||||
|
for i, req := range requests[nSigs:] {
|
||||||
|
require.False(t, bytes.Contains(completedTx.Scripts[0].InvocationScript, req.MainTransaction.Scripts[0].InvocationScript), fmt.Errorf("signature from extra request #%d shouldn't be presented in the main tx", i))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
require.Nil(t, completedTx, fmt.Errorf("main transaction shouldn't be completed: sent %d out of %d requests", sentCount, nSigs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkFallbackTxs := func(t *testing.T, requests []*payload.P2PNotaryRequest, shouldComplete bool) {
|
||||||
|
for i, req := range requests {
|
||||||
|
completedTx := completedTxes[req.FallbackTransaction.Hash()]
|
||||||
|
if shouldComplete {
|
||||||
|
require.NotNil(t, completedTx, fmt.Errorf("fallback transaction for request #%d expected to be completed", i))
|
||||||
|
require.Equal(t, 2, len(completedTx.Signers))
|
||||||
|
require.Equal(t, 2, len(completedTx.Scripts))
|
||||||
|
require.Equal(t, transaction.Witness{
|
||||||
|
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, acc1.PrivateKey().Sign(req.FallbackTransaction.GetSignedPart())...),
|
||||||
|
VerificationScript: []byte{},
|
||||||
|
}, completedTx.Scripts[0])
|
||||||
|
interopCtx := bc.newInteropContext(trigger.Verification, bc.dao, nil, completedTx)
|
||||||
|
_, err := bc.verifyHashAgainstScript(completedTx.Signers[1].Account, &completedTx.Scripts[1], interopCtx, -1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.Nil(t, completedTx, fmt.Errorf("fallback transaction for request #%d shouldn't be completed", i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkCompleteStandardRequest := func(t *testing.T, nKeys int, shouldComplete bool, nvbIncrements ...uint32) []*payload.P2PNotaryRequest {
|
||||||
|
requesters := make([]*wallet.Account, nKeys)
|
||||||
|
for i := range requesters {
|
||||||
|
requesters[i], _ = wallet.NewAccount()
|
||||||
|
}
|
||||||
|
|
||||||
|
requests := createStandardRequest(requesters, nvbIncrements...)
|
||||||
|
sendOrder := make([]int, nKeys)
|
||||||
|
for i := range sendOrder {
|
||||||
|
sendOrder[i] = i
|
||||||
|
}
|
||||||
|
rand.Shuffle(nKeys, func(i, j int) {
|
||||||
|
sendOrder[j], sendOrder[i] = sendOrder[i], sendOrder[j]
|
||||||
|
})
|
||||||
|
for i := range requests {
|
||||||
|
ntr1.OnNewRequest(requests[sendOrder[i]])
|
||||||
|
checkSigTx(t, requests, i+1, shouldComplete)
|
||||||
|
completedCount := len(completedTxes)
|
||||||
|
|
||||||
|
// check that the same request won't be processed twice
|
||||||
|
ntr1.OnNewRequest(requests[sendOrder[i]])
|
||||||
|
checkSigTx(t, requests, i+1, shouldComplete)
|
||||||
|
require.Equal(t, completedCount, len(completedTxes))
|
||||||
|
}
|
||||||
|
return requests
|
||||||
|
}
|
||||||
|
checkCompleteMultisigRequest := func(t *testing.T, nSigs int, nKeys int, shouldComplete bool) []*payload.P2PNotaryRequest {
|
||||||
|
requesters := make([]*wallet.Account, nKeys)
|
||||||
|
for i := range requesters {
|
||||||
|
requesters[i], _ = wallet.NewAccount()
|
||||||
|
}
|
||||||
|
requests := createMultisigRequest(nSigs, requesters)
|
||||||
|
sendOrder := make([]int, nKeys)
|
||||||
|
for i := range sendOrder {
|
||||||
|
sendOrder[i] = i
|
||||||
|
}
|
||||||
|
rand.Shuffle(nKeys, func(i, j int) {
|
||||||
|
sendOrder[j], sendOrder[i] = sendOrder[i], sendOrder[j]
|
||||||
|
})
|
||||||
|
|
||||||
|
var submittedRequests []*payload.P2PNotaryRequest
|
||||||
|
// sent only nSigs (m out of n) requests - it should be enough to complete min tx
|
||||||
|
for i := 0; i < nSigs; i++ {
|
||||||
|
submittedRequests = append(submittedRequests, requests[sendOrder[i]])
|
||||||
|
|
||||||
|
ntr1.OnNewRequest(requests[sendOrder[i]])
|
||||||
|
checkMultisigTx(t, nSigs, submittedRequests, i+1, shouldComplete)
|
||||||
|
|
||||||
|
// check that the same request won't be processed twice
|
||||||
|
ntr1.OnNewRequest(requests[sendOrder[i]])
|
||||||
|
checkMultisigTx(t, nSigs, submittedRequests, i+1, shouldComplete)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sent the rest (n-m) out of n requests: main tx is already collected, so only fallbacks should be applied
|
||||||
|
completedCount := len(completedTxes)
|
||||||
|
for i := nSigs; i < nKeys; i++ {
|
||||||
|
submittedRequests = append(submittedRequests, requests[sendOrder[i]])
|
||||||
|
|
||||||
|
ntr1.OnNewRequest(requests[sendOrder[i]])
|
||||||
|
checkMultisigTx(t, nSigs, submittedRequests, i+1, shouldComplete)
|
||||||
|
require.Equal(t, completedCount, len(completedTxes))
|
||||||
|
}
|
||||||
|
|
||||||
|
return submittedRequests
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnNewRequest: missing account
|
||||||
|
ntr1.UpdateNotaryNodes(keys.PublicKeys{randomAcc.PublicKey()})
|
||||||
|
r := checkCompleteStandardRequest(t, 1, false)
|
||||||
|
checkFallbackTxs(t, r, false)
|
||||||
|
// set account back for the next tests
|
||||||
|
ntr1.UpdateNotaryNodes(keys.PublicKeys{acc1.PrivateKey().PublicKey()})
|
||||||
|
|
||||||
|
// OnNewRequest: signature request
|
||||||
|
for _, i := range []int{1, 2, 3, 10} {
|
||||||
|
r := checkCompleteStandardRequest(t, i, true)
|
||||||
|
checkFallbackTxs(t, r, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnNewRequest: multisignature request
|
||||||
|
r = checkCompleteMultisigRequest(t, 1, 1, true)
|
||||||
|
checkFallbackTxs(t, r, false)
|
||||||
|
r = checkCompleteMultisigRequest(t, 1, 2, true)
|
||||||
|
checkFallbackTxs(t, r, false)
|
||||||
|
r = checkCompleteMultisigRequest(t, 1, 3, true)
|
||||||
|
checkFallbackTxs(t, r, false)
|
||||||
|
r = checkCompleteMultisigRequest(t, 3, 3, true)
|
||||||
|
checkFallbackTxs(t, r, false)
|
||||||
|
r = checkCompleteMultisigRequest(t, 3, 4, true)
|
||||||
|
checkFallbackTxs(t, r, false)
|
||||||
|
r = checkCompleteMultisigRequest(t, 3, 10, true)
|
||||||
|
checkFallbackTxs(t, r, false)
|
||||||
|
|
||||||
|
// PostPersist: missing account
|
||||||
|
finalizeWithError = true
|
||||||
|
r = checkCompleteStandardRequest(t, 1, false)
|
||||||
|
checkFallbackTxs(t, r, false)
|
||||||
|
finalizeWithError = false
|
||||||
|
ntr1.UpdateNotaryNodes(keys.PublicKeys{randomAcc.PublicKey()})
|
||||||
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||||
|
checkSigTx(t, r, 1, false)
|
||||||
|
checkFallbackTxs(t, r, false)
|
||||||
|
// set account back for the next tests
|
||||||
|
ntr1.UpdateNotaryNodes(keys.PublicKeys{acc1.PrivateKey().PublicKey()})
|
||||||
|
|
||||||
|
// PostPersist: complete main transaction, signature request
|
||||||
|
finalizeWithError = true
|
||||||
|
requests := checkCompleteStandardRequest(t, 3, false)
|
||||||
|
// check PostPersist with finalisation error
|
||||||
|
finalizeWithError = true
|
||||||
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||||
|
checkSigTx(t, requests, len(requests), false)
|
||||||
|
// check PostPersist without finalisation error
|
||||||
|
finalizeWithError = false
|
||||||
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||||
|
checkSigTx(t, requests, len(requests), true)
|
||||||
|
|
||||||
|
// PostPersist: complete main transaction, multisignature account
|
||||||
|
finalizeWithError = true
|
||||||
|
requests = checkCompleteMultisigRequest(t, 3, 4, false)
|
||||||
|
checkFallbackTxs(t, requests, false)
|
||||||
|
// check PostPersist with finalisation error
|
||||||
|
finalizeWithError = true
|
||||||
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||||
|
checkMultisigTx(t, 3, requests, len(requests), false)
|
||||||
|
checkFallbackTxs(t, requests, false)
|
||||||
|
// check PostPersist without finalisation error
|
||||||
|
finalizeWithError = false
|
||||||
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||||
|
checkMultisigTx(t, 3, requests, len(requests), true)
|
||||||
|
checkFallbackTxs(t, requests, false)
|
||||||
|
|
||||||
|
// PostPersist: complete fallback, signature request
|
||||||
|
finalizeWithError = true
|
||||||
|
requests = checkCompleteStandardRequest(t, 3, false)
|
||||||
|
checkFallbackTxs(t, requests, false)
|
||||||
|
// make fallbacks valid
|
||||||
|
_, err = bc.genBlocks(int(nvbDiffFallback))
|
||||||
|
require.NoError(t, err)
|
||||||
|
// check PostPersist for valid fallbacks with finalisation error
|
||||||
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||||
|
checkSigTx(t, requests, len(requests), false)
|
||||||
|
checkFallbackTxs(t, requests, false)
|
||||||
|
// check PostPersist for valid fallbacks without finalisation error
|
||||||
|
finalizeWithError = false
|
||||||
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||||
|
checkSigTx(t, requests, len(requests), false)
|
||||||
|
checkFallbackTxs(t, requests, true)
|
||||||
|
|
||||||
|
// PostPersist: complete fallback, multisignature request
|
||||||
|
nSigs, nKeys := 3, 5
|
||||||
|
// check OnNewRequest with finalization error
|
||||||
|
finalizeWithError = true
|
||||||
|
requests = checkCompleteMultisigRequest(t, nSigs, nKeys, false)
|
||||||
|
checkFallbackTxs(t, requests, false)
|
||||||
|
// make fallbacks valid
|
||||||
|
_, err = bc.genBlocks(int(nvbDiffFallback))
|
||||||
|
require.NoError(t, err)
|
||||||
|
// check PostPersist for valid fallbacks with finalisation error
|
||||||
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||||
|
checkMultisigTx(t, nSigs, requests, len(requests), false)
|
||||||
|
checkFallbackTxs(t, requests, false)
|
||||||
|
// check PostPersist for valid fallbacks without finalisation error
|
||||||
|
finalizeWithError = false
|
||||||
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||||
|
checkMultisigTx(t, nSigs, requests, len(requests), false)
|
||||||
|
checkFallbackTxs(t, requests[:nSigs], true)
|
||||||
|
// the rest of fallbacks should also be applied even if the main tx was already constructed by the moment they were sent
|
||||||
|
checkFallbackTxs(t, requests[nSigs:], true)
|
||||||
|
|
||||||
|
// PostPersist: partial fallbacks completion due to finalisation errors
|
||||||
|
finalizeWithError = true
|
||||||
|
requests = checkCompleteStandardRequest(t, 5, false)
|
||||||
|
checkFallbackTxs(t, requests, false)
|
||||||
|
// make fallbacks valid
|
||||||
|
_, err = bc.genBlocks(int(nvbDiffFallback))
|
||||||
|
require.NoError(t, err)
|
||||||
|
// some of fallbacks should fail finalisation
|
||||||
|
unluckies = []*payload.P2PNotaryRequest{requests[0], requests[4]}
|
||||||
|
lucky := requests[1:4]
|
||||||
|
choosy = true
|
||||||
|
// check PostPersist for lucky fallbacks
|
||||||
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||||
|
checkSigTx(t, requests, len(requests), false)
|
||||||
|
checkFallbackTxs(t, lucky, true)
|
||||||
|
checkFallbackTxs(t, unluckies, false)
|
||||||
|
// reset finalisation function for unlucky fallbacks to finalise without an error
|
||||||
|
choosy = false
|
||||||
|
finalizeWithError = false
|
||||||
|
// check PostPersist for unlucky fallbacks
|
||||||
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||||
|
checkSigTx(t, requests, len(requests), false)
|
||||||
|
checkFallbackTxs(t, lucky, true)
|
||||||
|
checkFallbackTxs(t, unluckies, true)
|
||||||
|
|
||||||
|
// PostPersist: different NVBs
|
||||||
|
// check OnNewRequest with finalization error and different NVBs
|
||||||
|
finalizeWithError = true
|
||||||
|
requests = checkCompleteStandardRequest(t, 5, false, 1, 2, 3, 4, 5)
|
||||||
|
checkFallbackTxs(t, requests, false)
|
||||||
|
// generate blocks to reach the most earlier fallback's NVB
|
||||||
|
_, err = bc.genBlocks(int(nvbDiffFallback))
|
||||||
|
require.NoError(t, err)
|
||||||
|
// check PostPersist for valid fallbacks without finalisation error
|
||||||
|
finalizeWithError = false
|
||||||
|
for i := range requests {
|
||||||
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||||
|
checkSigTx(t, requests, len(requests), false)
|
||||||
|
checkFallbackTxs(t, requests[:i+1], true)
|
||||||
|
checkFallbackTxs(t, requests[i+1:], false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnRequestRemoval: missing account
|
||||||
|
// check OnNewRequest with finalization error
|
||||||
|
finalizeWithError = true
|
||||||
|
requests = checkCompleteStandardRequest(t, 4, false)
|
||||||
|
checkFallbackTxs(t, requests, false)
|
||||||
|
// make fallbacks valid and remove one fallback
|
||||||
|
_, err = bc.genBlocks(int(nvbDiffFallback))
|
||||||
|
require.NoError(t, err)
|
||||||
|
ntr1.UpdateNotaryNodes(keys.PublicKeys{randomAcc.PublicKey()})
|
||||||
|
ntr1.OnRequestRemoval(requests[3])
|
||||||
|
// non of the fallbacks should be completed
|
||||||
|
finalizeWithError = false
|
||||||
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||||
|
checkSigTx(t, requests, len(requests), false)
|
||||||
|
checkFallbackTxs(t, requests, false)
|
||||||
|
// set account back for the next tests
|
||||||
|
ntr1.UpdateNotaryNodes(keys.PublicKeys{acc1.PrivateKey().PublicKey()})
|
||||||
|
|
||||||
|
// OnRequestRemoval: signature request, remove one fallback
|
||||||
|
// check OnNewRequest with finalization error
|
||||||
|
finalizeWithError = true
|
||||||
|
requests = checkCompleteStandardRequest(t, 4, false)
|
||||||
|
checkFallbackTxs(t, requests, false)
|
||||||
|
// make fallbacks valid and remove one fallback
|
||||||
|
_, err = bc.genBlocks(int(nvbDiffFallback))
|
||||||
|
require.NoError(t, err)
|
||||||
|
unlucky := requests[3]
|
||||||
|
ntr1.OnRequestRemoval(unlucky)
|
||||||
|
// rest of the fallbacks should be completed
|
||||||
|
finalizeWithError = false
|
||||||
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||||
|
checkSigTx(t, requests, len(requests), false)
|
||||||
|
checkFallbackTxs(t, requests[:3], true)
|
||||||
|
require.Nil(t, completedTxes[unlucky.FallbackTransaction.Hash()])
|
||||||
|
|
||||||
|
// OnRequestRemoval: signature request, remove all fallbacks
|
||||||
|
finalizeWithError = true
|
||||||
|
requests = checkCompleteStandardRequest(t, 4, false)
|
||||||
|
// remove all fallbacks
|
||||||
|
_, err = bc.genBlocks(int(nvbDiffFallback))
|
||||||
|
require.NoError(t, err)
|
||||||
|
for i := range requests {
|
||||||
|
ntr1.OnRequestRemoval(requests[i])
|
||||||
|
}
|
||||||
|
// then the whole request should be removed, i.e. there are no completed transactions
|
||||||
|
finalizeWithError = false
|
||||||
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||||
|
checkSigTx(t, requests, len(requests), false)
|
||||||
|
checkFallbackTxs(t, requests, false)
|
||||||
|
|
||||||
|
// OnRequestRemoval: signature request, remove unexisting fallback
|
||||||
|
ntr1.OnRequestRemoval(requests[0])
|
||||||
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||||
|
checkSigTx(t, requests, len(requests), false)
|
||||||
|
checkFallbackTxs(t, requests, false)
|
||||||
|
|
||||||
|
// OnRequestRemoval: multisignature request, remove one fallback
|
||||||
|
nSigs, nKeys = 3, 5
|
||||||
|
// check OnNewRequest with finalization error
|
||||||
|
finalizeWithError = true
|
||||||
|
requests = checkCompleteMultisigRequest(t, nSigs, nKeys, false)
|
||||||
|
checkMultisigTx(t, nSigs, requests, len(requests), false)
|
||||||
|
checkFallbackTxs(t, requests, false)
|
||||||
|
// make fallbacks valid and remove the last fallback
|
||||||
|
_, err = bc.genBlocks(int(nvbDiffFallback))
|
||||||
|
require.NoError(t, err)
|
||||||
|
unlucky = requests[nSigs-1]
|
||||||
|
ntr1.OnRequestRemoval(unlucky)
|
||||||
|
// then (m-1) out of n fallbacks should be completed
|
||||||
|
finalizeWithError = false
|
||||||
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||||
|
checkMultisigTx(t, nSigs, requests, len(requests), false)
|
||||||
|
checkFallbackTxs(t, requests[:nSigs-1], true)
|
||||||
|
require.Nil(t, completedTxes[unlucky.FallbackTransaction.Hash()])
|
||||||
|
// the rest (n-(m-1)) out of n fallbacks should also be completed even if main tx has been collected by the moment they were sent
|
||||||
|
checkFallbackTxs(t, requests[nSigs:], true)
|
||||||
|
|
||||||
|
// OnRequestRemoval: multisignature request, remove all fallbacks
|
||||||
|
finalizeWithError = true
|
||||||
|
requests = checkCompleteMultisigRequest(t, nSigs, nKeys, false)
|
||||||
|
// make fallbacks valid and then remove all of them
|
||||||
|
_, err = bc.genBlocks(int(nvbDiffFallback))
|
||||||
|
require.NoError(t, err)
|
||||||
|
for i := range requests {
|
||||||
|
ntr1.OnRequestRemoval(requests[i])
|
||||||
|
}
|
||||||
|
// then the whole request should be removed, i.e. there are no completed transactions
|
||||||
|
finalizeWithError = false
|
||||||
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||||
|
checkMultisigTx(t, nSigs, requests, len(requests), false)
|
||||||
|
checkFallbackTxs(t, requests, false)
|
||||||
|
|
||||||
|
// // OnRequestRemoval: multisignature request, remove unexisting fallbac, i.e. there still shouldn't be any completed transactions after this
|
||||||
|
ntr1.OnRequestRemoval(requests[0])
|
||||||
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||||
|
checkMultisigTx(t, nSigs, requests, len(requests), false)
|
||||||
|
checkFallbackTxs(t, requests, false)
|
||||||
|
|
||||||
|
// Subscriptions test
|
||||||
|
mp1.RunSubscriptions()
|
||||||
|
go ntr1.Run()
|
||||||
|
defer func() {
|
||||||
|
ntr1.Stop()
|
||||||
|
mp1.StopSubscriptions()
|
||||||
|
}()
|
||||||
|
finalizeWithError = false
|
||||||
|
requester1, _ := wallet.NewAccount()
|
||||||
|
requester2, _ := wallet.NewAccount()
|
||||||
|
amount := int64(100_0000_0000)
|
||||||
|
feer := NewNotaryFeerStub(bc)
|
||||||
|
transferTokenFromMultisigAccountCheckOK(t, bc, bc.GetNotaryContractScriptHash(), bc.contracts.GAS.Hash, amount, requester1.PrivateKey().PublicKey().GetScriptHash(), int64(bc.BlockHeight()+50))
|
||||||
|
checkBalanceOf(t, bc, bc.contracts.Notary.Hash, int(amount))
|
||||||
|
transferTokenFromMultisigAccountCheckOK(t, bc, bc.GetNotaryContractScriptHash(), bc.contracts.GAS.Hash, amount, requester2.PrivateKey().PublicKey().GetScriptHash(), int64(bc.BlockHeight()+50))
|
||||||
|
checkBalanceOf(t, bc, bc.contracts.Notary.Hash, int(2*amount))
|
||||||
|
// create request for 2 standard signatures => main tx should be completed after the second request is added to the pool
|
||||||
|
requests = createStandardRequest([]*wallet.Account{requester1, requester2})
|
||||||
|
require.NoError(t, mp1.Add(requests[0].FallbackTransaction, feer, requests[0]))
|
||||||
|
require.NoError(t, mp1.Add(requests[1].FallbackTransaction, feer, requests[1]))
|
||||||
|
require.Eventually(t, func() bool {
|
||||||
|
mtx.RLock()
|
||||||
|
defer mtx.RUnlock()
|
||||||
|
return completedTxes[requests[0].MainTransaction.Hash()] != nil
|
||||||
|
}, time.Second, time.Millisecond)
|
||||||
|
checkFallbackTxs(t, requests, false)
|
||||||
|
}
|
|
@ -4,13 +4,14 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/internal/fakechain"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"go.uber.org/zap/zaptest"
|
"go.uber.org/zap/zaptest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBlockQueue(t *testing.T) {
|
func TestBlockQueue(t *testing.T) {
|
||||||
chain := newTestChain()
|
chain := fakechain.NewFakeChain()
|
||||||
// notice, it's not yet running
|
// notice, it's not yet running
|
||||||
bq := newBlockQueue(0, chain, zaptest.NewLogger(t), nil)
|
bq := newBlockQueue(0, chain, zaptest.NewLogger(t), nil)
|
||||||
blocks := make([]*block.Block, 11)
|
blocks := make([]*block.Block, 11)
|
||||||
|
|
|
@ -1,333 +1,22 @@
|
||||||
package network
|
package network
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
"github.com/nspcc-dev/neo-go/internal/fakechain"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer/services"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/mempool"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
|
||||||
"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/crypto"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/network/capability"
|
"github.com/nspcc-dev/neo-go/pkg/network/capability"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/zap/zaptest"
|
"go.uber.org/zap/zaptest"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testChain struct {
|
|
||||||
config.ProtocolConfiguration
|
|
||||||
*mempool.Pool
|
|
||||||
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, 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},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (chain *testChain) putBlock(b *block.Block) {
|
|
||||||
chain.blocks[b.Hash()] = b
|
|
||||||
chain.hdrHashes[b.Index] = b.Hash()
|
|
||||||
atomic.StoreUint32(&chain.blockheight, b.Index)
|
|
||||||
}
|
|
||||||
func (chain *testChain) putHeader(b *block.Block) {
|
|
||||||
chain.hdrHashes[b.Index] = b.Hash()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (chain *testChain) putTx(tx *transaction.Transaction) {
|
|
||||||
chain.txs[tx.Hash()] = tx
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (*testChain) IsExtensibleAllowed(uint160 util.Uint160) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
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) GetBaseExecFee() int64 {
|
|
||||||
return interop.DefaultBaseExecFee
|
|
||||||
}
|
|
||||||
func (chain *testChain) GetStoragePrice() int64 {
|
|
||||||
return native.StoragePrice
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
func (chain *testChain) CalculateClaimable(util.Uint160, uint32) (*big.Int, error) {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (chain *testChain) FeePerByte() int64 {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (chain *testChain) P2PSigExtensionsEnabled() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (chain *testChain) GetMaxBlockSystemFee() int64 {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (chain *testChain) GetMaxBlockSize() uint32 {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (chain *testChain) AddHeaders(...*block.Header) error {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
func (chain *testChain) AddBlock(block *block.Block) error {
|
|
||||||
if block.Index == atomic.LoadUint32(&chain.blockheight)+1 {
|
|
||||||
chain.putBlock(block)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func (chain *testChain) AddStateRoot(r *state.MPTRoot) error {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
func (chain *testChain) BlockHeight() uint32 {
|
|
||||||
return atomic.LoadUint32(&chain.blockheight)
|
|
||||||
}
|
|
||||||
func (chain *testChain) Close() {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
func (chain *testChain) HeaderHeight() uint32 {
|
|
||||||
return atomic.LoadUint32(&chain.blockheight)
|
|
||||||
}
|
|
||||||
func (chain *testChain) GetAppExecResults(hash util.Uint256, trig trigger.Type) ([]state.AppExecResult, error) {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
func (chain *testChain) GetBlock(hash util.Uint256) (*block.Block, error) {
|
|
||||||
if b, ok := chain.blocks[hash]; ok {
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
return nil, errors.New("not found")
|
|
||||||
}
|
|
||||||
func (chain *testChain) GetCommittee() (keys.PublicKeys, error) {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
func (chain *testChain) GetContractState(hash util.Uint160) *state.Contract {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
func (chain *testChain) GetContractScriptHash(id int32) (util.Uint160, error) {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
func (chain *testChain) GetNativeContractScriptHash(name string) (util.Uint160, error) {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
func (chain *testChain) GetHeaderHash(n int) util.Uint256 {
|
|
||||||
return chain.hdrHashes[uint32(n)]
|
|
||||||
}
|
|
||||||
func (chain *testChain) GetHeader(hash util.Uint256) (*block.Header, error) {
|
|
||||||
b, err := chain.GetBlock(hash)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return b.Header(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (chain *testChain) GetNextBlockValidators() ([]*keys.PublicKey, error) {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
func (chain *testChain) ForEachNEP17Transfer(util.Uint160, func(*state.NEP17Transfer) (bool, error)) error {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
func (chain *testChain) GetNEP17Balances(util.Uint160) *state.NEP17Balances {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
func (chain *testChain) GetValidators() ([]*keys.PublicKey, error) {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
func (chain *testChain) GetStandByCommittee() keys.PublicKeys {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
func (chain *testChain) GetStandByValidators() keys.PublicKeys {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
func (chain *testChain) GetEnrollments() ([]state.Validator, error) {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
func (chain *testChain) GetStateProof(util.Uint256, []byte) ([][]byte, error) {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
func (chain *testChain) GetStateRoot(height uint32) (*state.MPTRootState, error) {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
func (chain *testChain) GetStorageItem(id int32, key []byte) *state.StorageItem {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
func (chain *testChain) GetTestVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) *vm.VM {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
func (chain *testChain) GetStorageItems(id int32) (map[string]*state.StorageItem, error) {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
func (chain *testChain) CurrentHeaderHash() util.Uint256 {
|
|
||||||
return util.Uint256{}
|
|
||||||
}
|
|
||||||
func (chain *testChain) CurrentBlockHash() util.Uint256 {
|
|
||||||
return util.Uint256{}
|
|
||||||
}
|
|
||||||
func (chain *testChain) HasBlock(h util.Uint256) bool {
|
|
||||||
_, ok := chain.blocks[h]
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
func (chain *testChain) HasTransaction(h util.Uint256) bool {
|
|
||||||
_, ok := chain.txs[h]
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
func (chain *testChain) GetTransaction(h util.Uint256) (*transaction.Transaction, uint32, error) {
|
|
||||||
if tx, ok := chain.txs[h]; ok {
|
|
||||||
return tx, 1, nil
|
|
||||||
}
|
|
||||||
return nil, 0, errors.New("not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (chain *testChain) GetMemPool() *mempool.Pool {
|
|
||||||
return chain.Pool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (chain *testChain) GetGoverningTokenBalance(acc util.Uint160) (*big.Int, uint32) {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (chain *testChain) GetUtilityTokenBalance(uint160 util.Uint160) *big.Int {
|
|
||||||
if chain.utilityTokenBalance != nil {
|
|
||||||
return chain.utilityTokenBalance
|
|
||||||
}
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
func (chain testChain) ManagementContractHash() util.Uint160 {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (chain *testChain) PoolTx(tx *transaction.Transaction, _ ...*mempool.Pool) error {
|
|
||||||
return chain.poolTx(tx)
|
|
||||||
}
|
|
||||||
func (chain testChain) SetOracle(services.Oracle) {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
func (chain *testChain) SubscribeForBlocks(ch chan<- *block.Block) {
|
|
||||||
chain.blocksCh = append(chain.blocksCh, ch)
|
|
||||||
}
|
|
||||||
func (chain *testChain) SubscribeForExecutions(ch chan<- *state.AppExecResult) {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
func (chain *testChain) SubscribeForNotifications(ch chan<- *state.NotificationEvent) {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
func (chain *testChain) SubscribeForTransactions(ch chan<- *transaction.Transaction) {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (chain *testChain) VerifyTx(*transaction.Transaction) error {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
func (chain *testChain) VerifyWitness(util.Uint160, crypto.Verifiable, *transaction.Witness, int64) error {
|
|
||||||
if chain.verifyWitnessF != nil {
|
|
||||||
return chain.verifyWitnessF()
|
|
||||||
}
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (chain *testChain) UnsubscribeFromBlocks(ch chan<- *block.Block) {
|
|
||||||
for i, c := range chain.blocksCh {
|
|
||||||
if c == ch {
|
|
||||||
if i < len(chain.blocksCh) {
|
|
||||||
copy(chain.blocksCh[i:], chain.blocksCh[i+1:])
|
|
||||||
}
|
|
||||||
chain.blocksCh = chain.blocksCh[:len(chain.blocksCh)]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (chain *testChain) UnsubscribeFromExecutions(ch chan<- *state.AppExecResult) {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
func (chain *testChain) UnsubscribeFromNotifications(ch chan<- *state.NotificationEvent) {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
func (chain *testChain) UnsubscribeFromTransactions(ch chan<- *transaction.Transaction) {
|
|
||||||
panic("TODO")
|
|
||||||
}
|
|
||||||
|
|
||||||
type testDiscovery struct {
|
type testDiscovery struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
bad []string
|
bad []string
|
||||||
|
@ -500,7 +189,7 @@ func (p *localPeer) CanProcessAddr() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestServer(t *testing.T, serverConfig ServerConfig) *Server {
|
func newTestServer(t *testing.T, serverConfig ServerConfig) *Server {
|
||||||
s, err := newServerFromConstructors(serverConfig, newTestChain(), zaptest.NewLogger(t),
|
s, err := newServerFromConstructors(serverConfig, fakechain.NewFakeChain(), zaptest.NewLogger(t),
|
||||||
newFakeTransp, newFakeConsensus, newTestDiscovery)
|
newFakeTransp, newFakeConsensus, newTestDiscovery)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return s
|
return s
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/network/capability"
|
"github.com/nspcc-dev/neo-go/pkg/network/capability"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/network/extpool"
|
"github.com/nspcc-dev/neo-go/pkg/network/extpool"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/services/notary"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/services/oracle"
|
"github.com/nspcc-dev/neo-go/pkg/services/oracle"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
|
@ -69,7 +70,8 @@ type (
|
||||||
consensus consensus.Service
|
consensus consensus.Service
|
||||||
notaryRequestPool *mempool.Pool
|
notaryRequestPool *mempool.Pool
|
||||||
extensiblePool *extpool.Pool
|
extensiblePool *extpool.Pool
|
||||||
NotaryFeer NotaryFeer
|
notaryFeer NotaryFeer
|
||||||
|
notaryModule *notary.Notary
|
||||||
|
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
peers map[Peer]bool
|
peers map[Peer]bool
|
||||||
|
@ -134,13 +136,32 @@ func newServerFromConstructors(config ServerConfig, chain blockchainer.Blockchai
|
||||||
transactions: make(chan *transaction.Transaction, 64),
|
transactions: make(chan *transaction.Transaction, 64),
|
||||||
}
|
}
|
||||||
if chain.P2PSigExtensionsEnabled() {
|
if chain.P2PSigExtensionsEnabled() {
|
||||||
s.NotaryFeer = NewNotaryFeer(chain)
|
s.notaryFeer = NewNotaryFeer(chain)
|
||||||
s.notaryRequestPool = mempool.New(chain.GetConfig().P2PNotaryRequestPayloadPoolSize, 1)
|
s.notaryRequestPool = mempool.New(chain.GetConfig().P2PNotaryRequestPayloadPoolSize, 1, chain.GetConfig().P2PNotary.Enabled)
|
||||||
chain.RegisterPostBlock(func(bc blockchainer.Blockchainer, txpool *mempool.Pool, _ *block.Block) {
|
chain.RegisterPostBlock(func(bc blockchainer.Blockchainer, txpool *mempool.Pool, _ *block.Block) {
|
||||||
s.notaryRequestPool.RemoveStale(func(t *transaction.Transaction) bool {
|
s.notaryRequestPool.RemoveStale(func(t *transaction.Transaction) bool {
|
||||||
return bc.IsTxStillRelevant(t, txpool, true)
|
return bc.IsTxStillRelevant(t, txpool, true)
|
||||||
}, s.NotaryFeer)
|
}, s.notaryFeer)
|
||||||
})
|
})
|
||||||
|
if chain.GetConfig().P2PNotary.Enabled {
|
||||||
|
n, err := notary.NewNotary(chain, s.notaryRequestPool, s.log, func(tx *transaction.Transaction) error {
|
||||||
|
r := s.RelayTxn(tx)
|
||||||
|
if r != RelaySucceed {
|
||||||
|
return fmt.Errorf("can't pool notary tx: hash %s, reason: %d", tx.Hash().StringLE(), byte(r))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create Notary module: %w", err)
|
||||||
|
}
|
||||||
|
s.notaryModule = n
|
||||||
|
chain.SetNotary(n)
|
||||||
|
chain.RegisterPostBlock(func(bc blockchainer.Blockchainer, pool *mempool.Pool, b *block.Block) {
|
||||||
|
s.notaryModule.PostPersist(bc, pool, b)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if chain.GetConfig().P2PNotary.Enabled {
|
||||||
|
return nil, errors.New("P2PSigExtensions are disabled, but Notary service is enable")
|
||||||
}
|
}
|
||||||
s.bQueue = newBlockQueue(maxBlockBatch, chain, log, func(b *block.Block) {
|
s.bQueue = newBlockQueue(maxBlockBatch, chain, log, func(b *block.Block) {
|
||||||
if !s.consensusStarted.Load() {
|
if !s.consensusStarted.Load() {
|
||||||
|
@ -235,6 +256,10 @@ func (s *Server) Start(errChan chan error) {
|
||||||
if s.oracle != nil {
|
if s.oracle != nil {
|
||||||
go s.oracle.Run()
|
go s.oracle.Run()
|
||||||
}
|
}
|
||||||
|
if s.notaryModule != nil {
|
||||||
|
s.notaryRequestPool.RunSubscriptions()
|
||||||
|
go s.notaryModule.Run()
|
||||||
|
}
|
||||||
go s.relayBlocksLoop()
|
go s.relayBlocksLoop()
|
||||||
go s.bQueue.run()
|
go s.bQueue.run()
|
||||||
go s.transport.Accept()
|
go s.transport.Accept()
|
||||||
|
@ -257,6 +282,10 @@ func (s *Server) Shutdown() {
|
||||||
if s.oracle != nil {
|
if s.oracle != nil {
|
||||||
s.oracle.Shutdown()
|
s.oracle.Shutdown()
|
||||||
}
|
}
|
||||||
|
if s.notaryModule != nil {
|
||||||
|
s.notaryModule.Stop()
|
||||||
|
s.notaryRequestPool.StopSubscriptions()
|
||||||
|
}
|
||||||
close(s.quit)
|
close(s.quit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -805,7 +834,7 @@ func (s *Server) handleP2PNotaryRequestCmd(r *payload.P2PNotaryRequest) error {
|
||||||
|
|
||||||
// verifyAndPoolNotaryRequest verifies NotaryRequest payload and adds it to the payload mempool.
|
// verifyAndPoolNotaryRequest verifies NotaryRequest payload and adds it to the payload mempool.
|
||||||
func (s *Server) verifyAndPoolNotaryRequest(r *payload.P2PNotaryRequest) RelayReason {
|
func (s *Server) verifyAndPoolNotaryRequest(r *payload.P2PNotaryRequest) RelayReason {
|
||||||
if err := s.chain.PoolTxWithData(r.FallbackTransaction, r, s.notaryRequestPool, s.NotaryFeer, verifyNotaryRequest); err != nil {
|
if err := s.chain.PoolTxWithData(r.FallbackTransaction, r, s.notaryRequestPool, s.notaryFeer, verifyNotaryRequest); err != nil {
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, core.ErrAlreadyExists):
|
case errors.Is(err, core.ErrAlreadyExists):
|
||||||
return RelayAlreadyExists
|
return RelayAlreadyExists
|
||||||
|
@ -827,7 +856,8 @@ func verifyNotaryRequest(bc blockchainer.Blockchainer, _ *transaction.Transactio
|
||||||
if err := bc.VerifyWitness(payer, r, &r.Witness, bc.GetPolicer().GetMaxVerificationGAS()); err != nil {
|
if err := bc.VerifyWitness(payer, r, &r.Witness, bc.GetPolicer().GetMaxVerificationGAS()); err != nil {
|
||||||
return fmt.Errorf("bad P2PNotaryRequest payload witness: %w", err)
|
return fmt.Errorf("bad P2PNotaryRequest payload witness: %w", err)
|
||||||
}
|
}
|
||||||
if r.FallbackTransaction.Sender() != bc.GetNotaryContractScriptHash() {
|
notaryHash := bc.GetNotaryContractScriptHash()
|
||||||
|
if r.FallbackTransaction.Sender() != notaryHash {
|
||||||
return errors.New("P2PNotary contract should be a sender of the fallback transaction")
|
return errors.New("P2PNotary contract should be a sender of the fallback transaction")
|
||||||
}
|
}
|
||||||
depositExpiration := bc.GetNotaryDepositExpiration(payer)
|
depositExpiration := bc.GetNotaryDepositExpiration(payer)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/internal/fakechain"
|
||||||
"github.com/nspcc-dev/neo-go/internal/random"
|
"github.com/nspcc-dev/neo-go/internal/random"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||||
|
@ -47,7 +48,7 @@ func (f *fakeConsensus) OnTransaction(tx *transaction.Transaction) { f.txs =
|
||||||
func (f *fakeConsensus) GetPayload(h util.Uint256) *payload.Extensible { panic("implement me") }
|
func (f *fakeConsensus) GetPayload(h util.Uint256) *payload.Extensible { panic("implement me") }
|
||||||
|
|
||||||
func TestNewServer(t *testing.T) {
|
func TestNewServer(t *testing.T) {
|
||||||
bc := &testChain{}
|
bc := &fakechain.FakeChain{}
|
||||||
s, err := newServerFromConstructors(ServerConfig{}, bc, nil, newFakeTransp, newFakeConsensus, newTestDiscovery)
|
s, err := newServerFromConstructors(ServerConfig{}, bc, nil, newFakeTransp, newFakeConsensus, newTestDiscovery)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
|
@ -223,7 +224,7 @@ func TestGetBlocksByIndex(t *testing.T) {
|
||||||
checkPingRespond(t, 3, 5000, 1+3*payload.MaxHashesCount)
|
checkPingRespond(t, 3, 5000, 1+3*payload.MaxHashesCount)
|
||||||
|
|
||||||
// Receive some blocks.
|
// Receive some blocks.
|
||||||
s.chain.(*testChain).blockheight = 2123
|
s.chain.(*fakechain.FakeChain).Blockheight = 2123
|
||||||
|
|
||||||
// Minimum chunk has priority.
|
// Minimum chunk has priority.
|
||||||
checkPingRespond(t, 5, 5000, 2124)
|
checkPingRespond(t, 5, 5000, 2124)
|
||||||
|
@ -392,7 +393,7 @@ func TestBlock(t *testing.T) {
|
||||||
s, shutdown := startTestServer(t)
|
s, shutdown := startTestServer(t)
|
||||||
defer shutdown()
|
defer shutdown()
|
||||||
|
|
||||||
atomic2.StoreUint32(&s.chain.(*testChain).blockheight, 12344)
|
atomic2.StoreUint32(&s.chain.(*fakechain.FakeChain).Blockheight, 12344)
|
||||||
require.Equal(t, uint32(12344), s.chain.BlockHeight())
|
require.Equal(t, uint32(12344), s.chain.BlockHeight())
|
||||||
|
|
||||||
b := block.New(netmode.UnitTestNet, false)
|
b := block.New(netmode.UnitTestNet, false)
|
||||||
|
@ -405,7 +406,7 @@ func TestConsensus(t *testing.T) {
|
||||||
s, shutdown := startTestServer(t)
|
s, shutdown := startTestServer(t)
|
||||||
defer shutdown()
|
defer shutdown()
|
||||||
|
|
||||||
atomic2.StoreUint32(&s.chain.(*testChain).blockheight, 4)
|
atomic2.StoreUint32(&s.chain.(*fakechain.FakeChain).Blockheight, 4)
|
||||||
p := newLocalPeer(t, s)
|
p := newLocalPeer(t, s)
|
||||||
p.handshaked = true
|
p.handshaked = true
|
||||||
|
|
||||||
|
@ -417,11 +418,11 @@ func TestConsensus(t *testing.T) {
|
||||||
return NewMessage(CMDExtensible, pl)
|
return NewMessage(CMDExtensible, pl)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.chain.(*testChain).verifyWitnessF = func() error { return errors.New("invalid") }
|
s.chain.(*fakechain.FakeChain).VerifyWitnessF = func() error { return errors.New("invalid") }
|
||||||
msg := newConsensusMessage(0, s.chain.BlockHeight()+1)
|
msg := newConsensusMessage(0, s.chain.BlockHeight()+1)
|
||||||
require.Error(t, s.handleMessage(p, msg))
|
require.Error(t, s.handleMessage(p, msg))
|
||||||
|
|
||||||
s.chain.(*testChain).verifyWitnessF = func() error { return nil }
|
s.chain.(*fakechain.FakeChain).VerifyWitnessF = func() error { return nil }
|
||||||
require.NoError(t, s.handleMessage(p, msg))
|
require.NoError(t, s.handleMessage(p, msg))
|
||||||
require.Contains(t, s.consensus.(*fakeConsensus).payloads, msg.Payload.(*payload.Extensible))
|
require.Contains(t, s.consensus.(*fakeConsensus).payloads, msg.Payload.(*payload.Extensible))
|
||||||
|
|
||||||
|
@ -471,7 +472,7 @@ func TestTransaction(t *testing.T) {
|
||||||
})
|
})
|
||||||
t.Run("bad", func(t *testing.T) {
|
t.Run("bad", func(t *testing.T) {
|
||||||
tx := newDummyTx()
|
tx := newDummyTx()
|
||||||
s.chain.(*testChain).poolTx = func(*transaction.Transaction) error { return core.ErrInsufficientFunds }
|
s.chain.(*fakechain.FakeChain).PoolTxF = func(*transaction.Transaction) error { return core.ErrInsufficientFunds }
|
||||||
s.testHandleMessage(t, nil, CMDTX, tx)
|
s.testHandleMessage(t, nil, CMDTX, tx)
|
||||||
for _, ftx := range s.consensus.(*fakeConsensus).txs {
|
for _, ftx := range s.consensus.(*fakeConsensus).txs {
|
||||||
require.NotEqual(t, ftx, tx)
|
require.NotEqual(t, ftx, tx)
|
||||||
|
@ -505,19 +506,19 @@ func (s *Server) testHandleGetData(t *testing.T, invType payload.InventoryType,
|
||||||
func TestGetData(t *testing.T) {
|
func TestGetData(t *testing.T) {
|
||||||
s, shutdown := startTestServer(t)
|
s, shutdown := startTestServer(t)
|
||||||
defer shutdown()
|
defer shutdown()
|
||||||
s.chain.(*testChain).utilityTokenBalance = big.NewInt(1000000)
|
s.chain.(*fakechain.FakeChain).UtilityTokenBalance = big.NewInt(1000000)
|
||||||
|
|
||||||
t.Run("block", func(t *testing.T) {
|
t.Run("block", func(t *testing.T) {
|
||||||
b := newDummyBlock(2, 0)
|
b := newDummyBlock(2, 0)
|
||||||
hs := []util.Uint256{random.Uint256(), b.Hash(), random.Uint256()}
|
hs := []util.Uint256{random.Uint256(), b.Hash(), random.Uint256()}
|
||||||
s.chain.(*testChain).putBlock(b)
|
s.chain.(*fakechain.FakeChain).PutBlock(b)
|
||||||
notFound := []util.Uint256{hs[0], hs[2]}
|
notFound := []util.Uint256{hs[0], hs[2]}
|
||||||
s.testHandleGetData(t, payload.BlockType, hs, notFound, b)
|
s.testHandleGetData(t, payload.BlockType, hs, notFound, b)
|
||||||
})
|
})
|
||||||
t.Run("transaction", func(t *testing.T) {
|
t.Run("transaction", func(t *testing.T) {
|
||||||
tx := newDummyTx()
|
tx := newDummyTx()
|
||||||
hs := []util.Uint256{random.Uint256(), tx.Hash(), random.Uint256()}
|
hs := []util.Uint256{random.Uint256(), tx.Hash(), random.Uint256()}
|
||||||
s.chain.(*testChain).putTx(tx)
|
s.chain.(*fakechain.FakeChain).PutTx(tx)
|
||||||
notFound := []util.Uint256{hs[0], hs[2]}
|
notFound := []util.Uint256{hs[0], hs[2]}
|
||||||
s.testHandleGetData(t, payload.TXType, hs, notFound, tx)
|
s.testHandleGetData(t, payload.TXType, hs, notFound, tx)
|
||||||
})
|
})
|
||||||
|
@ -567,7 +568,7 @@ func initGetBlocksTest(t *testing.T) (*Server, func(), []*block.Block) {
|
||||||
var blocks []*block.Block
|
var blocks []*block.Block
|
||||||
for i := uint32(12); i <= 15; i++ {
|
for i := uint32(12); i <= 15; i++ {
|
||||||
b := newDummyBlock(i, 3)
|
b := newDummyBlock(i, 3)
|
||||||
s.chain.(*testChain).putBlock(b)
|
s.chain.(*fakechain.FakeChain).PutBlock(b)
|
||||||
blocks = append(blocks, b)
|
blocks = append(blocks, b)
|
||||||
}
|
}
|
||||||
return s, shutdown, blocks
|
return s, shutdown, blocks
|
||||||
|
@ -633,7 +634,7 @@ func TestGetBlockByIndex(t *testing.T) {
|
||||||
s.testHandleMessage(t, p, CMDGetBlockByIndex, &payload.GetBlockByIndex{IndexStart: blocks[0].Index, Count: -1})
|
s.testHandleMessage(t, p, CMDGetBlockByIndex, &payload.GetBlockByIndex{IndexStart: blocks[0].Index, Count: -1})
|
||||||
})
|
})
|
||||||
t.Run("-1, last header", func(t *testing.T) {
|
t.Run("-1, last header", func(t *testing.T) {
|
||||||
s.chain.(*testChain).putHeader(newDummyBlock(16, 2))
|
s.chain.(*fakechain.FakeChain).PutHeader(newDummyBlock(16, 2))
|
||||||
actual = nil
|
actual = nil
|
||||||
expected = blocks
|
expected = blocks
|
||||||
s.testHandleMessage(t, p, CMDGetBlockByIndex, &payload.GetBlockByIndex{IndexStart: blocks[0].Index, Count: -1})
|
s.testHandleMessage(t, p, CMDGetBlockByIndex, &payload.GetBlockByIndex{IndexStart: blocks[0].Index, Count: -1})
|
||||||
|
@ -683,7 +684,7 @@ func TestGetHeaders(t *testing.T) {
|
||||||
func TestInv(t *testing.T) {
|
func TestInv(t *testing.T) {
|
||||||
s, shutdown := startTestServer(t)
|
s, shutdown := startTestServer(t)
|
||||||
defer shutdown()
|
defer shutdown()
|
||||||
s.chain.(*testChain).utilityTokenBalance = big.NewInt(10000000)
|
s.chain.(*fakechain.FakeChain).UtilityTokenBalance = big.NewInt(10000000)
|
||||||
|
|
||||||
var actual []util.Uint256
|
var actual []util.Uint256
|
||||||
p := newLocalPeer(t, s)
|
p := newLocalPeer(t, s)
|
||||||
|
@ -696,7 +697,7 @@ func TestInv(t *testing.T) {
|
||||||
|
|
||||||
t.Run("blocks", func(t *testing.T) {
|
t.Run("blocks", func(t *testing.T) {
|
||||||
b := newDummyBlock(10, 3)
|
b := newDummyBlock(10, 3)
|
||||||
s.chain.(*testChain).putBlock(b)
|
s.chain.(*fakechain.FakeChain).PutBlock(b)
|
||||||
hs := []util.Uint256{random.Uint256(), b.Hash(), random.Uint256()}
|
hs := []util.Uint256{random.Uint256(), b.Hash(), random.Uint256()}
|
||||||
s.testHandleMessage(t, p, CMDInv, &payload.Inventory{
|
s.testHandleMessage(t, p, CMDInv, &payload.Inventory{
|
||||||
Type: payload.BlockType,
|
Type: payload.BlockType,
|
||||||
|
@ -706,7 +707,7 @@ func TestInv(t *testing.T) {
|
||||||
})
|
})
|
||||||
t.Run("transaction", func(t *testing.T) {
|
t.Run("transaction", func(t *testing.T) {
|
||||||
tx := newDummyTx()
|
tx := newDummyTx()
|
||||||
s.chain.(*testChain).putTx(tx)
|
s.chain.(*fakechain.FakeChain).PutTx(tx)
|
||||||
hs := []util.Uint256{random.Uint256(), tx.Hash(), random.Uint256()}
|
hs := []util.Uint256{random.Uint256(), tx.Hash(), random.Uint256()}
|
||||||
s.testHandleMessage(t, p, CMDInv, &payload.Inventory{
|
s.testHandleMessage(t, p, CMDInv, &payload.Inventory{
|
||||||
Type: payload.TXType,
|
Type: payload.TXType,
|
||||||
|
@ -716,8 +717,8 @@ func TestInv(t *testing.T) {
|
||||||
})
|
})
|
||||||
t.Run("extensible", func(t *testing.T) {
|
t.Run("extensible", func(t *testing.T) {
|
||||||
ep := payload.NewExtensible(netmode.UnitTestNet)
|
ep := payload.NewExtensible(netmode.UnitTestNet)
|
||||||
s.chain.(*testChain).verifyWitnessF = func() error { return nil }
|
s.chain.(*fakechain.FakeChain).VerifyWitnessF = func() error { return nil }
|
||||||
ep.ValidBlockEnd = s.chain.(*testChain).BlockHeight() + 1
|
ep.ValidBlockEnd = s.chain.(*fakechain.FakeChain).BlockHeight() + 1
|
||||||
ok, err := s.extensiblePool.Add(ep)
|
ok, err := s.extensiblePool.Add(ep)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
@ -865,7 +866,7 @@ func TestMemPool(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bc := s.chain.(*testChain)
|
bc := s.chain.(*fakechain.FakeChain)
|
||||||
expected := make([]util.Uint256, 4)
|
expected := make([]util.Uint256, 4)
|
||||||
for i := range expected {
|
for i := range expected {
|
||||||
tx := newDummyTx()
|
tx := newDummyTx()
|
||||||
|
@ -878,27 +879,27 @@ func TestMemPool(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVerifyNotaryRequest(t *testing.T) {
|
func TestVerifyNotaryRequest(t *testing.T) {
|
||||||
bc := newTestChain()
|
bc := fakechain.NewFakeChain()
|
||||||
bc.maxVerificationGAS = 10
|
bc.MaxVerificationGAS = 10
|
||||||
bc.notaryContractScriptHash = util.Uint160{1, 2, 3}
|
bc.NotaryContractScriptHash = util.Uint160{1, 2, 3}
|
||||||
newNotaryRequest := func() *payload.P2PNotaryRequest {
|
newNotaryRequest := func() *payload.P2PNotaryRequest {
|
||||||
return &payload.P2PNotaryRequest{
|
return &payload.P2PNotaryRequest{
|
||||||
MainTransaction: &transaction.Transaction{Script: []byte{0, 1, 2}},
|
MainTransaction: &transaction.Transaction{Script: []byte{0, 1, 2}},
|
||||||
FallbackTransaction: &transaction.Transaction{
|
FallbackTransaction: &transaction.Transaction{
|
||||||
ValidUntilBlock: 321,
|
ValidUntilBlock: 321,
|
||||||
Signers: []transaction.Signer{{Account: bc.notaryContractScriptHash}, {Account: random.Uint160()}},
|
Signers: []transaction.Signer{{Account: bc.NotaryContractScriptHash}, {Account: random.Uint160()}},
|
||||||
},
|
},
|
||||||
Witness: transaction.Witness{},
|
Witness: transaction.Witness{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("bad payload witness", func(t *testing.T) {
|
t.Run("bad payload witness", func(t *testing.T) {
|
||||||
bc.verifyWitnessF = func() error { return errors.New("bad witness") }
|
bc.VerifyWitnessF = func() error { return errors.New("bad witness") }
|
||||||
require.Error(t, verifyNotaryRequest(bc, nil, newNotaryRequest()))
|
require.Error(t, verifyNotaryRequest(bc, nil, newNotaryRequest()))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("bad fallback sender", func(t *testing.T) {
|
t.Run("bad fallback sender", func(t *testing.T) {
|
||||||
bc.verifyWitnessF = func() error { return nil }
|
bc.VerifyWitnessF = func() error { return nil }
|
||||||
r := newNotaryRequest()
|
r := newNotaryRequest()
|
||||||
r.FallbackTransaction.Signers[0] = transaction.Signer{Account: util.Uint160{7, 8, 9}}
|
r.FallbackTransaction.Signers[0] = transaction.Signer{Account: util.Uint160{7, 8, 9}}
|
||||||
require.Error(t, verifyNotaryRequest(bc, nil, r))
|
require.Error(t, verifyNotaryRequest(bc, nil, r))
|
||||||
|
@ -906,13 +907,13 @@ func TestVerifyNotaryRequest(t *testing.T) {
|
||||||
|
|
||||||
t.Run("expired deposit", func(t *testing.T) {
|
t.Run("expired deposit", func(t *testing.T) {
|
||||||
r := newNotaryRequest()
|
r := newNotaryRequest()
|
||||||
bc.notaryDepositExpiration = r.FallbackTransaction.ValidUntilBlock
|
bc.NotaryDepositExpiration = r.FallbackTransaction.ValidUntilBlock
|
||||||
require.Error(t, verifyNotaryRequest(bc, nil, r))
|
require.Error(t, verifyNotaryRequest(bc, nil, r))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("good", func(t *testing.T) {
|
t.Run("good", func(t *testing.T) {
|
||||||
r := newNotaryRequest()
|
r := newNotaryRequest()
|
||||||
bc.notaryDepositExpiration = r.FallbackTransaction.ValidUntilBlock + 1
|
bc.NotaryDepositExpiration = r.FallbackTransaction.ValidUntilBlock + 1
|
||||||
require.NoError(t, verifyNotaryRequest(bc, nil, r))
|
require.NoError(t, verifyNotaryRequest(bc, nil, r))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
54
pkg/services/notary/node.go
Normal file
54
pkg/services/notary/node.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package notary
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UpdateNotaryNodes implements Notary interface and updates current notary account.
|
||||||
|
func (n *Notary) UpdateNotaryNodes(notaryNodes keys.PublicKeys) {
|
||||||
|
n.accMtx.Lock()
|
||||||
|
defer n.accMtx.Unlock()
|
||||||
|
|
||||||
|
if n.currAccount != nil {
|
||||||
|
for _, node := range notaryNodes {
|
||||||
|
if node.Equal(n.currAccount.PrivateKey().PublicKey()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var acc *wallet.Account
|
||||||
|
for _, node := range notaryNodes {
|
||||||
|
acc = n.wallet.GetAccount(node.GetScriptHash())
|
||||||
|
if acc != nil {
|
||||||
|
if acc.PrivateKey() != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
err := acc.Decrypt(n.Config.MainCfg.UnlockWallet.Password)
|
||||||
|
if err != nil {
|
||||||
|
n.Config.Log.Warn("can't unlock notary node account",
|
||||||
|
zap.String("address", address.Uint160ToString(acc.Contract.ScriptHash())),
|
||||||
|
zap.Error(err))
|
||||||
|
acc = nil
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n.currAccount = acc
|
||||||
|
if acc == nil {
|
||||||
|
n.reqMtx.Lock()
|
||||||
|
n.requests = make(map[util.Uint256]*request)
|
||||||
|
n.reqMtx.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Notary) getAccount() *wallet.Account {
|
||||||
|
n.accMtx.RLock()
|
||||||
|
defer n.accMtx.RUnlock()
|
||||||
|
return n.currAccount
|
||||||
|
}
|
71
pkg/services/notary/node_test.go
Normal file
71
pkg/services/notary/node_test.go
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
package notary
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/internal/fakechain"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
|
"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/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"go.uber.org/zap/zaptest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getTestNotary(t *testing.T, bc blockchainer.Blockchainer, walletPath, pass string) (*wallet.Account, *Notary, *mempool.Pool) {
|
||||||
|
bc.(*fakechain.FakeChain).ProtocolConfiguration.P2PNotary = config.P2PNotary{
|
||||||
|
Enabled: true,
|
||||||
|
UnlockWallet: config.Wallet{
|
||||||
|
Path: walletPath,
|
||||||
|
Password: pass,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mp := mempool.New(10, 1, true)
|
||||||
|
ntr, err := NewNotary(bc, mp, zaptest.NewLogger(t), nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
w, err := wallet.NewWalletFromFile(walletPath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, w.Accounts[0].Decrypt(pass))
|
||||||
|
return w.Accounts[0], ntr, mp
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateNotaryNodes(t *testing.T) {
|
||||||
|
bc := fakechain.NewFakeChain()
|
||||||
|
acc, ntr, _ := getTestNotary(t, bc, "./testdata/notary1.json", "one")
|
||||||
|
randomKey, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
// currAcc is nil before UpdateNotaryNodes call
|
||||||
|
require.Nil(t, ntr.currAccount)
|
||||||
|
// set account for the first time
|
||||||
|
ntr.UpdateNotaryNodes(keys.PublicKeys{acc.PrivateKey().PublicKey()})
|
||||||
|
require.Equal(t, acc, ntr.currAccount)
|
||||||
|
|
||||||
|
t.Run("account is already set", func(t *testing.T) {
|
||||||
|
ntr.UpdateNotaryNodes(keys.PublicKeys{acc.PrivateKey().PublicKey(), randomKey.PublicKey()})
|
||||||
|
require.Equal(t, acc, ntr.currAccount)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("another account from the same wallet", func(t *testing.T) {
|
||||||
|
t.Run("good config password", func(t *testing.T) {
|
||||||
|
w, err := wallet.NewWalletFromFile("./testdata/notary1.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, w.Accounts[1].Decrypt("one"))
|
||||||
|
ntr.UpdateNotaryNodes(keys.PublicKeys{w.Accounts[1].PrivateKey().PublicKey()})
|
||||||
|
require.Equal(t, w.Accounts[1], ntr.currAccount)
|
||||||
|
})
|
||||||
|
t.Run("bad config password", func(t *testing.T) {
|
||||||
|
w, err := wallet.NewWalletFromFile("./testdata/notary1.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, w.Accounts[2].Decrypt("four"))
|
||||||
|
ntr.UpdateNotaryNodes(keys.PublicKeys{w.Accounts[2].PrivateKey().PublicKey()})
|
||||||
|
require.Nil(t, ntr.currAccount)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("unknown account", func(t *testing.T) {
|
||||||
|
ntr.UpdateNotaryNodes(keys.PublicKeys{randomKey.PublicKey()})
|
||||||
|
require.Nil(t, ntr.currAccount)
|
||||||
|
})
|
||||||
|
}
|
394
pkg/services/notary/notary.go
Normal file
394
pkg/services/notary/notary.go
Normal file
|
@ -0,0 +1,394 @@
|
||||||
|
package notary
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"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/mempool"
|
||||||
|
"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/crypto/keys"
|
||||||
|
"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"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Notary represents Notary module.
|
||||||
|
Notary struct {
|
||||||
|
Config Config
|
||||||
|
|
||||||
|
// onTransaction is a callback for completed transactions (mains or fallbacks) sending.
|
||||||
|
onTransaction func(tx *transaction.Transaction) error
|
||||||
|
|
||||||
|
// reqMtx protects requests list.
|
||||||
|
reqMtx sync.RWMutex
|
||||||
|
// requests represents the map of main transactions which needs to be completed
|
||||||
|
// with the associated fallback transactions grouped by the main transaction hash
|
||||||
|
requests map[util.Uint256]*request
|
||||||
|
|
||||||
|
// accMtx protects account.
|
||||||
|
accMtx sync.RWMutex
|
||||||
|
currAccount *wallet.Account
|
||||||
|
wallet *wallet.Wallet
|
||||||
|
|
||||||
|
mp *mempool.Pool
|
||||||
|
// requests channel
|
||||||
|
reqCh chan mempool.Event
|
||||||
|
stopCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config represents external configuration for Notary module.
|
||||||
|
Config struct {
|
||||||
|
MainCfg config.P2PNotary
|
||||||
|
Chain blockchainer.Blockchainer
|
||||||
|
Log *zap.Logger
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// request represents Notary service request.
|
||||||
|
type request struct {
|
||||||
|
typ RequestType
|
||||||
|
// isSent indicates whether main transaction was successfully sent to the network.
|
||||||
|
isSent bool
|
||||||
|
main *transaction.Transaction
|
||||||
|
// minNotValidBefore is the minimum NVB value among fallbacks transactions.
|
||||||
|
// We stop trying to send mainTx to the network if the chain reaches minNotValidBefore height.
|
||||||
|
minNotValidBefore uint32
|
||||||
|
fallbacks []*transaction.Transaction
|
||||||
|
// nSigs is the number of signatures to be collected.
|
||||||
|
// nSigs == nKeys for standard signature request;
|
||||||
|
// nSigs <= nKeys for multisignature request.
|
||||||
|
// nSigs is 0 when all received requests were invalid, so check request.typ before access to nSigs.
|
||||||
|
nSigs uint8
|
||||||
|
// nSigsCollected is the number of already collected signatures
|
||||||
|
nSigsCollected uint8
|
||||||
|
|
||||||
|
// sigs is a map of partial multisig invocation scripts [opcode.PUSHDATA1+64+signatureBytes] grouped by public keys
|
||||||
|
sigs map[*keys.PublicKey][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNotary returns new Notary module.
|
||||||
|
func NewNotary(bc blockchainer.Blockchainer, mp *mempool.Pool, log *zap.Logger, onTransaction func(tx *transaction.Transaction) error) (*Notary, error) {
|
||||||
|
cfg := bc.GetConfig().P2PNotary
|
||||||
|
w := cfg.UnlockWallet
|
||||||
|
wallet, err := wallet.NewWalletFromFile(w.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
haveAccount := false
|
||||||
|
for _, acc := range wallet.Accounts {
|
||||||
|
if err := acc.Decrypt(w.Password); err == nil {
|
||||||
|
haveAccount = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !haveAccount {
|
||||||
|
return nil, errors.New("no wallet account could be unlocked")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Notary{
|
||||||
|
requests: make(map[util.Uint256]*request),
|
||||||
|
Config: Config{
|
||||||
|
MainCfg: cfg,
|
||||||
|
Chain: bc,
|
||||||
|
Log: log,
|
||||||
|
},
|
||||||
|
wallet: wallet,
|
||||||
|
onTransaction: onTransaction,
|
||||||
|
mp: mp,
|
||||||
|
reqCh: make(chan mempool.Event),
|
||||||
|
stopCh: make(chan struct{}),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run runs Notary module and should be called in a separate goroutine.
|
||||||
|
func (n *Notary) Run() {
|
||||||
|
n.mp.SubscribeForTransactions(n.reqCh)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-n.stopCh:
|
||||||
|
n.mp.UnsubscribeFromTransactions(n.reqCh)
|
||||||
|
return
|
||||||
|
case event := <-n.reqCh:
|
||||||
|
if req, ok := event.Data.(*payload.P2PNotaryRequest); ok {
|
||||||
|
switch event.Type {
|
||||||
|
case mempool.TransactionAdded:
|
||||||
|
n.OnNewRequest(req)
|
||||||
|
case mempool.TransactionRemoved:
|
||||||
|
n.OnRequestRemoval(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop shutdowns Notary module.
|
||||||
|
func (n *Notary) Stop() {
|
||||||
|
close(n.stopCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnNewRequest is a callback method which is called after new notary request is added to the notary request pool.
|
||||||
|
func (n *Notary) OnNewRequest(payload *payload.P2PNotaryRequest) {
|
||||||
|
if n.getAccount() == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nvbFallback := payload.FallbackTransaction.GetAttributes(transaction.NotValidBeforeT)[0].Value.(*transaction.NotValidBefore).Height
|
||||||
|
nKeys := payload.MainTransaction.GetAttributes(transaction.NotaryAssistedT)[0].Value.(*transaction.NotaryAssisted).NKeys
|
||||||
|
typ, nSigs, pubs, validationErr := n.verifyIncompleteWitnesses(payload.MainTransaction, nKeys)
|
||||||
|
n.reqMtx.Lock()
|
||||||
|
defer n.reqMtx.Unlock()
|
||||||
|
r, exists := n.requests[payload.MainTransaction.Hash()]
|
||||||
|
if exists {
|
||||||
|
for _, fb := range r.fallbacks {
|
||||||
|
if fb.Hash().Equals(payload.FallbackTransaction.Hash()) {
|
||||||
|
return // then we already have processed this request
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if nvbFallback < r.minNotValidBefore {
|
||||||
|
r.minNotValidBefore = nvbFallback
|
||||||
|
}
|
||||||
|
if r.typ == Unknown && validationErr == nil {
|
||||||
|
r.typ = typ
|
||||||
|
r.nSigs = nSigs
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
r = &request{
|
||||||
|
nSigs: nSigs,
|
||||||
|
main: payload.MainTransaction,
|
||||||
|
typ: typ,
|
||||||
|
minNotValidBefore: nvbFallback,
|
||||||
|
}
|
||||||
|
n.requests[payload.MainTransaction.Hash()] = r
|
||||||
|
}
|
||||||
|
r.fallbacks = append(r.fallbacks, payload.FallbackTransaction)
|
||||||
|
if exists && r.typ != Unknown && r.nSigsCollected >= r.nSigs { // already collected sufficient number of signatures to complete main transaction
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if validationErr == nil {
|
||||||
|
loop:
|
||||||
|
for i, w := range payload.MainTransaction.Scripts {
|
||||||
|
if payload.MainTransaction.Signers[i].Account.Equals(n.Config.Chain.GetNotaryContractScriptHash()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(w.InvocationScript) != 0 && len(w.VerificationScript) != 0 {
|
||||||
|
switch r.typ {
|
||||||
|
case Signature:
|
||||||
|
if !exists {
|
||||||
|
r.nSigsCollected++
|
||||||
|
} else if len(r.main.Scripts[i].InvocationScript) == 0 { // need this check because signature can already be added (consider receiving the same payload multiple times)
|
||||||
|
r.main.Scripts[i] = w
|
||||||
|
r.nSigsCollected++
|
||||||
|
}
|
||||||
|
if r.nSigsCollected == r.nSigs {
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
case MultiSignature:
|
||||||
|
if r.sigs == nil {
|
||||||
|
r.sigs = make(map[*keys.PublicKey][]byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := r.main.GetSignedHash().BytesBE()
|
||||||
|
for _, pub := range pubs {
|
||||||
|
if r.sigs[pub] != nil {
|
||||||
|
continue // signature for this pub has already been added
|
||||||
|
}
|
||||||
|
if pub.Verify(w.InvocationScript[2:], hash) { // then pub is the owner of the signature
|
||||||
|
r.sigs[pub] = w.InvocationScript
|
||||||
|
r.nSigsCollected++
|
||||||
|
if r.nSigsCollected == r.nSigs {
|
||||||
|
var invScript []byte
|
||||||
|
for j := range pubs {
|
||||||
|
if sig, ok := r.sigs[pubs[j]]; ok {
|
||||||
|
invScript = append(invScript, sig...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.main.Scripts[i].InvocationScript = invScript
|
||||||
|
}
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// pubKey was not found for the signature i.e. signature is bad - we're OK with that, let the fallback TX to be added
|
||||||
|
break loop // only one multisignature is allowed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r.typ != Unknown && r.nSigsCollected == nSigs && r.minNotValidBefore > n.Config.Chain.BlockHeight() {
|
||||||
|
if err := n.finalize(r.main); err != nil {
|
||||||
|
n.Config.Log.Error("failed to finalize main transaction", zap.Error(err))
|
||||||
|
} else {
|
||||||
|
r.isSent = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnRequestRemoval is a callback which is called after fallback transaction is removed
|
||||||
|
// from the notary payload pool due to expiration, main tx appliance or any other reason.
|
||||||
|
func (n *Notary) OnRequestRemoval(pld *payload.P2PNotaryRequest) {
|
||||||
|
if n.getAccount() == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n.reqMtx.Lock()
|
||||||
|
defer n.reqMtx.Unlock()
|
||||||
|
r, ok := n.requests[pld.MainTransaction.Hash()]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i, fb := range r.fallbacks {
|
||||||
|
if fb.Hash().Equals(pld.FallbackTransaction.Hash()) {
|
||||||
|
r.fallbacks = append(r.fallbacks[:i], r.fallbacks[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(r.fallbacks) == 0 {
|
||||||
|
delete(n.requests, r.main.Hash())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostPersist is a callback which is called after new block is persisted.
|
||||||
|
func (n *Notary) PostPersist(bc blockchainer.Blockchainer, pool *mempool.Pool, b *block.Block) {
|
||||||
|
if n.getAccount() == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n.reqMtx.Lock()
|
||||||
|
defer n.reqMtx.Unlock()
|
||||||
|
for h, r := range n.requests {
|
||||||
|
if !r.isSent && r.typ != Unknown && r.nSigs == r.nSigsCollected && r.minNotValidBefore > bc.BlockHeight() {
|
||||||
|
if err := n.finalize(r.main); err != nil {
|
||||||
|
n.Config.Log.Error("failed to finalize main transaction", zap.Error(err))
|
||||||
|
} else {
|
||||||
|
r.isSent = true
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if r.minNotValidBefore <= bc.BlockHeight() { // then at least one of the fallbacks can already be sent.
|
||||||
|
newFallbacks := r.fallbacks[:0]
|
||||||
|
for _, fb := range r.fallbacks {
|
||||||
|
if nvb := fb.GetAttributes(transaction.NotValidBeforeT)[0].Value.(*transaction.NotValidBefore).Height; nvb <= bc.BlockHeight() {
|
||||||
|
if err := n.finalize(fb); err != nil {
|
||||||
|
newFallbacks = append(newFallbacks, fb) // wait for the next block to resend them
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newFallbacks = append(newFallbacks, fb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(newFallbacks) == 0 {
|
||||||
|
delete(n.requests, h)
|
||||||
|
} else {
|
||||||
|
r.fallbacks = newFallbacks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finalize adds missing Notary witnesses to the transaction (main or fallback) and pushes it to the network.
|
||||||
|
func (n *Notary) finalize(tx *transaction.Transaction) error {
|
||||||
|
acc := n.getAccount()
|
||||||
|
if acc == nil {
|
||||||
|
panic(errors.New("no available Notary account")) // unreachable code, because all callers of `finalize` check that acc != nil
|
||||||
|
}
|
||||||
|
notaryWitness := transaction.Witness{
|
||||||
|
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, acc.PrivateKey().Sign(tx.GetSignedPart())...),
|
||||||
|
VerificationScript: []byte{},
|
||||||
|
}
|
||||||
|
for i, signer := range tx.Signers {
|
||||||
|
if signer.Account == n.Config.Chain.GetNotaryContractScriptHash() {
|
||||||
|
tx.Scripts[i] = notaryWitness
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n.onTransaction(tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyIncompleteWitnesses checks that tx either doesn't have all witnesses attached (in this case none of them
|
||||||
|
// can be multisignature), or it only has a partial multisignature. It returns the request type (sig/multisig), the
|
||||||
|
// number of signatures to be collected, sorted public keys (for multisig request only) and an error.
|
||||||
|
func (n *Notary) verifyIncompleteWitnesses(tx *transaction.Transaction, nKeys uint8) (RequestType, uint8, keys.PublicKeys, error) {
|
||||||
|
var (
|
||||||
|
typ RequestType
|
||||||
|
nSigs int
|
||||||
|
nKeysActual uint8
|
||||||
|
pubsBytes [][]byte
|
||||||
|
pubs keys.PublicKeys
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
|
if len(tx.Signers) < 2 {
|
||||||
|
return Unknown, 0, nil, errors.New("transaction should have at least 2 signers")
|
||||||
|
}
|
||||||
|
if len(tx.Signers) != len(tx.Scripts) {
|
||||||
|
return Unknown, 0, nil, fmt.Errorf("transaction should have %d witnesses attached (completed + dummy)", len(tx.Signers))
|
||||||
|
}
|
||||||
|
if !tx.HasSigner(n.Config.Chain.GetNotaryContractScriptHash()) {
|
||||||
|
return Unknown, 0, nil, fmt.Errorf("P2PNotary contract should be a signer of the transaction")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, w := range tx.Scripts {
|
||||||
|
// do not check witness for Notary contract -- it will be replaced by proper witness in any case.
|
||||||
|
if tx.Signers[i].Account == n.Config.Chain.GetNotaryContractScriptHash() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(w.VerificationScript) == 0 {
|
||||||
|
// then it's a contract verification (can be combined with anything)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !tx.Signers[i].Account.Equals(hash.Hash160(w.VerificationScript)) { // https://github.com/nspcc-dev/neo-go/pull/1658#discussion_r564265987
|
||||||
|
return Unknown, 0, nil, fmt.Errorf("transaction should have valid verification script for signer #%d", i)
|
||||||
|
}
|
||||||
|
if nSigs, pubsBytes, ok = vm.ParseMultiSigContract(w.VerificationScript); ok {
|
||||||
|
if typ == Signature || typ == MultiSignature {
|
||||||
|
return Unknown, 0, nil, fmt.Errorf("bad type of witness #%d: only one multisignature witness is allowed", i)
|
||||||
|
}
|
||||||
|
typ = MultiSignature
|
||||||
|
nKeysActual = uint8(len(pubsBytes))
|
||||||
|
if len(w.InvocationScript) != 66 || !bytes.HasPrefix(w.InvocationScript, []byte{byte(opcode.PUSHDATA1), 64}) {
|
||||||
|
return Unknown, 0, nil, fmt.Errorf("multisignature invocation script should have length = 66 and be of the form [PUSHDATA1, 64, signatureBytes...]")
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if vm.IsSignatureContract(w.VerificationScript) {
|
||||||
|
if typ == MultiSignature {
|
||||||
|
return Unknown, 0, nil, fmt.Errorf("bad type of witness #%d: multisignature witness can not be combined with other witnesses", i)
|
||||||
|
}
|
||||||
|
typ = Signature
|
||||||
|
nSigs = int(nKeys)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return Unknown, 0, nil, fmt.Errorf("unable to define the type of witness #%d", i)
|
||||||
|
}
|
||||||
|
switch typ {
|
||||||
|
case Signature:
|
||||||
|
if len(tx.Scripts) < int(nKeys+1) {
|
||||||
|
return Unknown, 0, nil, fmt.Errorf("transaction should comtain at least %d witnesses (1 for notary + nKeys)", nKeys+1)
|
||||||
|
}
|
||||||
|
case MultiSignature:
|
||||||
|
if nKeysActual != nKeys {
|
||||||
|
return Unknown, 0, nil, fmt.Errorf("bad m out of n partial multisignature witness: expected n = %d, got n = %d", nKeys, nKeysActual)
|
||||||
|
}
|
||||||
|
pubs = make(keys.PublicKeys, len(pubsBytes))
|
||||||
|
for i, pBytes := range pubsBytes {
|
||||||
|
pub, err := keys.NewPublicKeyFromBytes(pBytes, elliptic.P256())
|
||||||
|
if err != nil {
|
||||||
|
return Unknown, 0, nil, fmt.Errorf("invalid bytes of #%d public key: %s", i, hex.EncodeToString(pBytes))
|
||||||
|
}
|
||||||
|
pubs[i] = pub
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return Unknown, 0, nil, errors.New("unexpected Notary request type")
|
||||||
|
}
|
||||||
|
return typ, uint8(nSigs), pubs, nil
|
||||||
|
}
|
427
pkg/services/notary/notary_test.go
Normal file
427
pkg/services/notary/notary_test.go
Normal file
|
@ -0,0 +1,427 @@
|
||||||
|
package notary
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/internal/fakechain"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
|
"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/crypto/hash"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
"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/zap/zaptest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWallet(t *testing.T) {
|
||||||
|
bc := fakechain.NewFakeChain()
|
||||||
|
|
||||||
|
t.Run("unexisting wallet", func(t *testing.T) {
|
||||||
|
bc.ProtocolConfiguration.P2PNotary = config.P2PNotary{
|
||||||
|
Enabled: true,
|
||||||
|
UnlockWallet: config.Wallet{
|
||||||
|
Path: "./testdata/does_not_exists.json",
|
||||||
|
Password: "one",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err := NewNotary(bc, mempool.New(1, 1, true), zaptest.NewLogger(t), nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad password", func(t *testing.T) {
|
||||||
|
bc.ProtocolConfiguration.P2PNotary = config.P2PNotary{
|
||||||
|
Enabled: true,
|
||||||
|
UnlockWallet: config.Wallet{
|
||||||
|
Path: "./testdata/notary1.json",
|
||||||
|
Password: "invalid",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err := NewNotary(bc, mempool.New(1, 1, true), zaptest.NewLogger(t), nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("good", func(t *testing.T) {
|
||||||
|
bc.ProtocolConfiguration.P2PNotary = config.P2PNotary{
|
||||||
|
Enabled: true,
|
||||||
|
UnlockWallet: config.Wallet{
|
||||||
|
Path: "./testdata/notary1.json",
|
||||||
|
Password: "one",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err := NewNotary(bc, mempool.New(1, 1, true), zaptest.NewLogger(t), nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyIncompleteRequest(t *testing.T) {
|
||||||
|
bc := fakechain.NewFakeChain()
|
||||||
|
notaryContractHash := util.Uint160{1, 2, 3}
|
||||||
|
bc.NotaryContractScriptHash = notaryContractHash
|
||||||
|
_, ntr, _ := getTestNotary(t, bc, "./testdata/notary1.json", "one")
|
||||||
|
sig := append([]byte{byte(opcode.PUSHDATA1), 64}, make([]byte, 64)...) // we're not interested in signature correctness
|
||||||
|
acc1, _ := keys.NewPrivateKey()
|
||||||
|
acc2, _ := keys.NewPrivateKey()
|
||||||
|
acc3, _ := keys.NewPrivateKey()
|
||||||
|
sigScript1 := acc1.PublicKey().GetVerificationScript()
|
||||||
|
sigScript2 := acc2.PublicKey().GetVerificationScript()
|
||||||
|
sigScript3 := acc3.PublicKey().GetVerificationScript()
|
||||||
|
multisigScript1, err := smartcontract.CreateMultiSigRedeemScript(1, keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()})
|
||||||
|
require.NoError(t, err)
|
||||||
|
multisigScriptHash1 := hash.Hash160(multisigScript1)
|
||||||
|
multisigScript2, err := smartcontract.CreateMultiSigRedeemScript(2, keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()})
|
||||||
|
require.NoError(t, err)
|
||||||
|
multisigScriptHash2 := hash.Hash160(multisigScript2)
|
||||||
|
|
||||||
|
checkErr := func(t *testing.T, tx *transaction.Transaction, nKeys uint8) {
|
||||||
|
typ, nSigs, pubs, err := ntr.verifyIncompleteWitnesses(tx, nKeys)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, Unknown, typ)
|
||||||
|
require.Equal(t, uint8(0), nSigs)
|
||||||
|
require.Nil(t, pubs)
|
||||||
|
}
|
||||||
|
|
||||||
|
errCases := map[string]struct {
|
||||||
|
tx *transaction.Transaction
|
||||||
|
nKeys uint8
|
||||||
|
}{
|
||||||
|
"not enough signers": {
|
||||||
|
tx: &transaction.Transaction{
|
||||||
|
Signers: []transaction.Signer{{Account: notaryContractHash}},
|
||||||
|
Scripts: []transaction.Witness{{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"signers count and witnesses count mismatch": {
|
||||||
|
tx: &transaction.Transaction{
|
||||||
|
Signers: []transaction.Signer{{Account: notaryContractHash}, {}},
|
||||||
|
Scripts: []transaction.Witness{{}, {}, {}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"missing Notary witness": {
|
||||||
|
tx: &transaction.Transaction{
|
||||||
|
Signers: []transaction.Signer{{Account: acc1.GetScriptHash()}, {Account: acc2.GetScriptHash()}},
|
||||||
|
Scripts: []transaction.Witness{{}, {}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"unknown witness type": {
|
||||||
|
tx: &transaction.Transaction{
|
||||||
|
Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: notaryContractHash}},
|
||||||
|
Scripts: []transaction.Witness{
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"bad verification script": {
|
||||||
|
tx: &transaction.Transaction{
|
||||||
|
Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: notaryContractHash}},
|
||||||
|
Scripts: []transaction.Witness{
|
||||||
|
{
|
||||||
|
InvocationScript: []byte{},
|
||||||
|
VerificationScript: []byte{1, 2, 3},
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"several multisig witnesses": {
|
||||||
|
tx: &transaction.Transaction{
|
||||||
|
Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: multisigScriptHash2}, {Account: notaryContractHash}},
|
||||||
|
Scripts: []transaction.Witness{
|
||||||
|
{
|
||||||
|
InvocationScript: sig,
|
||||||
|
VerificationScript: multisigScript1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
InvocationScript: sig,
|
||||||
|
VerificationScript: multisigScript2,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nKeys: 2,
|
||||||
|
},
|
||||||
|
"multisig + sig": {
|
||||||
|
tx: &transaction.Transaction{
|
||||||
|
Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: acc1.PublicKey().GetScriptHash()}, {Account: notaryContractHash}},
|
||||||
|
Scripts: []transaction.Witness{
|
||||||
|
{
|
||||||
|
InvocationScript: sig,
|
||||||
|
VerificationScript: multisigScript1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
InvocationScript: sig,
|
||||||
|
VerificationScript: sigScript1,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nKeys: 2,
|
||||||
|
},
|
||||||
|
"sig + multisig": {
|
||||||
|
tx: &transaction.Transaction{
|
||||||
|
Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: multisigScriptHash1}, {Account: notaryContractHash}},
|
||||||
|
Scripts: []transaction.Witness{
|
||||||
|
{
|
||||||
|
InvocationScript: sig,
|
||||||
|
VerificationScript: sigScript1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
InvocationScript: sig,
|
||||||
|
VerificationScript: multisigScript1,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nKeys: 2,
|
||||||
|
},
|
||||||
|
"empty multisig + sig": {
|
||||||
|
tx: &transaction.Transaction{
|
||||||
|
Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: acc1.PublicKey().GetScriptHash()}, {Account: notaryContractHash}},
|
||||||
|
Scripts: []transaction.Witness{
|
||||||
|
{
|
||||||
|
InvocationScript: []byte{},
|
||||||
|
VerificationScript: multisigScript1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
InvocationScript: sig,
|
||||||
|
VerificationScript: sigScript1,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nKeys: 2,
|
||||||
|
},
|
||||||
|
"sig + empty multisig": {
|
||||||
|
tx: &transaction.Transaction{
|
||||||
|
Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: multisigScriptHash1}, {Account: notaryContractHash}},
|
||||||
|
Scripts: []transaction.Witness{
|
||||||
|
{
|
||||||
|
InvocationScript: sig,
|
||||||
|
VerificationScript: sigScript1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
InvocationScript: []byte{},
|
||||||
|
VerificationScript: multisigScript1,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nKeys: 2,
|
||||||
|
},
|
||||||
|
"multisig + empty sig": {
|
||||||
|
tx: &transaction.Transaction{
|
||||||
|
Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: acc1.PublicKey().GetScriptHash()}, {Account: notaryContractHash}},
|
||||||
|
Scripts: []transaction.Witness{
|
||||||
|
{
|
||||||
|
InvocationScript: sig,
|
||||||
|
VerificationScript: multisigScript1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
InvocationScript: []byte{},
|
||||||
|
VerificationScript: sigScript1,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nKeys: 2,
|
||||||
|
},
|
||||||
|
"empty sig + multisig": {
|
||||||
|
tx: &transaction.Transaction{
|
||||||
|
Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: multisigScriptHash1}, {Account: notaryContractHash}},
|
||||||
|
Scripts: []transaction.Witness{
|
||||||
|
{
|
||||||
|
InvocationScript: []byte{},
|
||||||
|
VerificationScript: sigScript1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
InvocationScript: sig,
|
||||||
|
VerificationScript: multisigScript1,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nKeys: 2,
|
||||||
|
},
|
||||||
|
"sig: bad nKeys": {
|
||||||
|
tx: &transaction.Transaction{
|
||||||
|
Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: acc2.PublicKey().GetScriptHash()}, {Account: notaryContractHash}},
|
||||||
|
Scripts: []transaction.Witness{
|
||||||
|
{
|
||||||
|
InvocationScript: sig,
|
||||||
|
VerificationScript: sigScript1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
InvocationScript: sig,
|
||||||
|
VerificationScript: sigScript2,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nKeys: 3,
|
||||||
|
},
|
||||||
|
"multisig: bad witnesses count": {
|
||||||
|
tx: &transaction.Transaction{
|
||||||
|
Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: notaryContractHash}},
|
||||||
|
Scripts: []transaction.Witness{
|
||||||
|
{
|
||||||
|
InvocationScript: sig,
|
||||||
|
VerificationScript: multisigScript1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nKeys: 2,
|
||||||
|
},
|
||||||
|
"multisig: bad nKeys": {
|
||||||
|
tx: &transaction.Transaction{
|
||||||
|
Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: notaryContractHash}},
|
||||||
|
Scripts: []transaction.Witness{
|
||||||
|
{
|
||||||
|
InvocationScript: sig,
|
||||||
|
VerificationScript: multisigScript1,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nKeys: 2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, errCase := range errCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
checkErr(t, errCase.tx, errCase.nKeys)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
tx *transaction.Transaction
|
||||||
|
nKeys uint8
|
||||||
|
expectedType RequestType
|
||||||
|
expectedNSigs uint8
|
||||||
|
expectedPubs keys.PublicKeys
|
||||||
|
}{
|
||||||
|
"single sig": {
|
||||||
|
tx: &transaction.Transaction{
|
||||||
|
Signers: []transaction.Signer{{Account: acc1.GetScriptHash()}, {Account: notaryContractHash}},
|
||||||
|
Scripts: []transaction.Witness{
|
||||||
|
{
|
||||||
|
InvocationScript: sig,
|
||||||
|
VerificationScript: sigScript1,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nKeys: 1,
|
||||||
|
expectedType: Signature,
|
||||||
|
expectedNSigs: 1,
|
||||||
|
},
|
||||||
|
"multiple sig": {
|
||||||
|
tx: &transaction.Transaction{
|
||||||
|
Signers: []transaction.Signer{{Account: acc1.GetScriptHash()}, {Account: acc2.GetScriptHash()}, {Account: acc3.GetScriptHash()}, {Account: notaryContractHash}},
|
||||||
|
Scripts: []transaction.Witness{
|
||||||
|
{
|
||||||
|
InvocationScript: sig,
|
||||||
|
VerificationScript: sigScript1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
InvocationScript: []byte{},
|
||||||
|
VerificationScript: []byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
InvocationScript: sig,
|
||||||
|
VerificationScript: sigScript3,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nKeys: 3,
|
||||||
|
expectedType: Signature,
|
||||||
|
expectedNSigs: 3,
|
||||||
|
},
|
||||||
|
"multisig 1 out of 3": {
|
||||||
|
tx: &transaction.Transaction{
|
||||||
|
Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: notaryContractHash}},
|
||||||
|
Scripts: []transaction.Witness{
|
||||||
|
{
|
||||||
|
InvocationScript: sig,
|
||||||
|
VerificationScript: multisigScript1,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nKeys: 3,
|
||||||
|
expectedType: MultiSignature,
|
||||||
|
expectedNSigs: 1,
|
||||||
|
expectedPubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()},
|
||||||
|
},
|
||||||
|
"multisig 2 out of 3": {
|
||||||
|
tx: &transaction.Transaction{
|
||||||
|
Signers: []transaction.Signer{{Account: multisigScriptHash2}, {Account: notaryContractHash}},
|
||||||
|
Scripts: []transaction.Witness{
|
||||||
|
{
|
||||||
|
InvocationScript: sig,
|
||||||
|
VerificationScript: multisigScript2,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nKeys: 3,
|
||||||
|
expectedType: MultiSignature,
|
||||||
|
expectedNSigs: 2,
|
||||||
|
expectedPubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()},
|
||||||
|
},
|
||||||
|
"empty + multisig": {
|
||||||
|
tx: &transaction.Transaction{
|
||||||
|
Signers: []transaction.Signer{{Account: acc1.PublicKey().GetScriptHash()}, {Account: multisigScriptHash1}, {Account: notaryContractHash}},
|
||||||
|
Scripts: []transaction.Witness{
|
||||||
|
{
|
||||||
|
InvocationScript: []byte{},
|
||||||
|
VerificationScript: []byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
InvocationScript: sig,
|
||||||
|
VerificationScript: multisigScript1,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nKeys: 3,
|
||||||
|
expectedType: MultiSignature,
|
||||||
|
expectedNSigs: 1,
|
||||||
|
expectedPubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()},
|
||||||
|
},
|
||||||
|
"multisig + empty": {
|
||||||
|
tx: &transaction.Transaction{
|
||||||
|
Signers: []transaction.Signer{{Account: multisigScriptHash1}, {Account: acc1.PublicKey().GetScriptHash()}, {Account: notaryContractHash}},
|
||||||
|
Scripts: []transaction.Witness{
|
||||||
|
{
|
||||||
|
InvocationScript: sig,
|
||||||
|
VerificationScript: multisigScript1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
InvocationScript: []byte{},
|
||||||
|
VerificationScript: []byte{},
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nKeys: 3,
|
||||||
|
expectedType: MultiSignature,
|
||||||
|
expectedNSigs: 1,
|
||||||
|
expectedPubs: keys.PublicKeys{acc1.PublicKey(), acc2.PublicKey(), acc3.PublicKey()},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
typ, nSigs, pubs, err := ntr.verifyIncompleteWitnesses(testCase.tx, testCase.nKeys)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, testCase.expectedType, typ)
|
||||||
|
assert.Equal(t, testCase.expectedNSigs, nSigs)
|
||||||
|
assert.ElementsMatch(t, testCase.expectedPubs, pubs)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
13
pkg/services/notary/request_type.go
Normal file
13
pkg/services/notary/request_type.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package notary
|
||||||
|
|
||||||
|
// RequestType represents the type of Notary request.
|
||||||
|
type RequestType byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Unknown represents unknown request type which means that main tx witnesses are invalid.
|
||||||
|
Unknown RequestType = 0x00
|
||||||
|
// Signature represents standard single signature request type.
|
||||||
|
Signature RequestType = 0x01
|
||||||
|
// MultiSignature represents m out of n multisignature request type.
|
||||||
|
MultiSignature RequestType = 0x02
|
||||||
|
)
|
1
pkg/services/notary/testdata/notary1.json
vendored
Normal file
1
pkg/services/notary/testdata/notary1.json
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"version":"3.0","accounts":[{"address":"NSbjd7dSePTZ6QpADAuM5722QpBmL5124W","key":"6PYVWTfkNCYvyQhyFLHH5dyRyT6jSi8u8Z8kn122PACfsDWi4QgkGm8FyW","label":"NotaryNode1","contract":{"script":"DCEDm5PmbOfVPmYXTSVW903XnOhhNBTsF9oDlVYusIH/ui0LQZVEDXg=","parameters":[{"name":"parameter0","type":"Signature"}],"deployed":false},"lock":false,"isdefault":false},{"address":"NisSvmSd2Lp28tjr8EqCZB5ahHDvBExo2j","key":"6PYLvgnZNwhiiZPiSCw3B3bHSFwbSXgh3MkGt4gL69MD8Sw7LMnuUgM9KQ","label":"three","contract":{"script":"DCEDHRWEIGXHCwUU2Fc7B0qrYPezXR0sfdEduRExyzIKVC8LQZVEDXg=","parameters":[{"name":"parameter0","type":"Signature"}],"deployed":false},"lock":false,"isdefault":false},{"address":"NRCCdGifyUWKnFotZhcgKpmxhhVJSJb94r","key":"6PYKXkuJ7G6bTj62bjy8fsBLF5okYNdAEBhKPCv8nmcALCtk2yPtBo835p","label":"four","contract":{"script":"DCECmUfs/gqKHd3AdJm5+Ev6zkubV8pP8DZzgu8+t5WdphILQZVEDXg=","parameters":[{"name":"parameter0","type":"Signature"}],"deployed":false},"lock":false,"isdefault":false}],"scrypt":{"n":16384,"r":8,"p":8},"extra":{"Tokens":null}}
|
1
pkg/services/notary/testdata/notary2.json
vendored
Normal file
1
pkg/services/notary/testdata/notary2.json
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"version":"3.0","accounts":[{"address":"NfFcJvWcHe8SSS92hNZhyQUJ6cg3pb36Tf","key":"6PYU2QoD52Xt9Z6QmNGUJWn89qUD1W6QqAL4Y8nfTWtTKvmVpQh8wsH6qY","label":"NotaryNode2","contract":{"script":"DCECIcKj0GFdv4b1NZrw9X6zLNLWzmNKAxtw6olIMZxpPRQLQZVEDXg=","parameters":[{"name":"parameter0","type":"Signature"}],"deployed":false},"lock":false,"isdefault":false}],"scrypt":{"n":16384,"r":8,"p":8},"extra":{"Tokens":null}}
|
Loading…
Reference in a new issue