Skip to content

Commit

Permalink
network: add notary request payload
Browse files Browse the repository at this point in the history
  • Loading branch information
AnnaShaleva committed Dec 4, 2020
1 parent b047334 commit 9cbf6a0
Show file tree
Hide file tree
Showing 21 changed files with 1,658 additions and 122 deletions.
5 changes: 4 additions & 1 deletion pkg/config/protocol_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ type (
ProtocolConfiguration struct {
Magic netmode.Magic `yaml:"Magic"`
MemPoolSize int `yaml:"MemPoolSize"`
// P2PNotaryRequestPayloadPoolSize specifies the memory pool size for P2PNotaryRequestPayloads.
// It is valid only if P2PSigExtensions are enabled.
P2PNotaryRequestPayloadPoolSize int `yaml:"P2PNotaryRequestPayloadPoolSize"`
// KeepOnlyLatestState specifies if MPT should only store latest state.
// If true, DB size will be smaller, but older roots won't be accessible.
// This value should remain the same for the same database.
Expand All @@ -17,7 +20,7 @@ type (
RemoveUntraceableBlocks bool `yaml:"RemoveUntraceableBlocks"`
// MaxTraceableBlocks is the length of the chain accessible to smart contracts.
MaxTraceableBlocks uint32 `yaml:"MaxTraceableBlocks"`
// P2PSigExtensions enables additional signature-related transaction attributes
// P2PSigExtensions enables additional signature-related logic.
P2PSigExtensions bool `yaml:"P2PSigExtensions"`
// ReservedAttributes allows to have reserved attributes range for experimental or private purposes.
ReservedAttributes bool `yaml:"ReservedAttributes"`
Expand Down
4 changes: 2 additions & 2 deletions pkg/consensus/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ func (s *service) verifyBlock(b block.Block) bool {
s.log.Warn("proposed block has already outdated")
return false
}
maxBlockSize := int(s.Chain.GetMaxBlockSize())
maxBlockSize := int(s.Chain.GetPolicer().GetMaxBlockSize())
size := io.GetVarSize(coreb)
if size > maxBlockSize {
s.log.Warn("proposed block size exceeds policy max block size",
Expand Down Expand Up @@ -454,7 +454,7 @@ func (s *service) verifyBlock(b block.Block) bool {
}
}

maxBlockSysFee := s.Chain.GetMaxBlockSystemFee()
maxBlockSysFee := s.Chain.GetPolicer().GetMaxBlockSystemFee()
if fee > maxBlockSysFee {
s.log.Warn("proposed block system fee exceeds policy max block system fee",
zap.Int("max system fee allowed", int(maxBlockSysFee)),
Expand Down
4 changes: 2 additions & 2 deletions pkg/consensus/consensus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ func TestVerifyBlock(t *testing.T) {
require.False(t, srv.verifyBlock(&neoBlock{Block: *b}))
})
t.Run("bad big size", func(t *testing.T) {
script := make([]byte, int(srv.Chain.GetMaxBlockSize()))
script := make([]byte, int(srv.Chain.GetPolicer().GetMaxBlockSize()))
script[0] = byte(opcode.RET)
tx := transaction.New(netmode.UnitTestNet, script, 100000)
tx.ValidUntilBlock = 1
Expand All @@ -407,7 +407,7 @@ func TestVerifyBlock(t *testing.T) {
t.Run("bad big sys fee", func(t *testing.T) {
txes := make([]*transaction.Transaction, 2)
for i := range txes {
txes[i] = transaction.New(netmode.UnitTestNet, []byte{byte(opcode.RET)}, srv.Chain.GetMaxBlockSystemFee()/2+1)
txes[i] = transaction.New(netmode.UnitTestNet, []byte{byte(opcode.RET)}, srv.Chain.GetPolicer().GetMaxBlockSystemFee()/2+1)
txes[i].ValidUntilBlock = 1
addSender(t, txes[i])
signTx(t, srv.Chain.FeePerByte(), txes[i])
Expand Down
89 changes: 76 additions & 13 deletions pkg/core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
"github.com/nspcc-dev/neo-go/pkg/core/dao"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/mempool"
Expand Down Expand Up @@ -42,9 +43,10 @@ const (
headerBatchCount = 2000
version = "0.1.0"

defaultMemPoolSize = 50000
defaultMaxTraceableBlocks = 2102400 // 1 year of 15s blocks
verificationGasLimit = 100000000 // 1 GAS
defaultMemPoolSize = 50000
defaultP2PNotaryRequestPayloadPoolSize = 1000
defaultMaxTraceableBlocks = 2102400 // 1 year of 15s blocks
verificationGasLimit = 100000000 // 1 GAS
)

var (
Expand Down Expand Up @@ -116,6 +118,10 @@ type Blockchain struct {

memPool *mempool.Pool

// postBlock is a set of callback methods which should be run under the Blockchain lock after new block is persisted.
// Block's transactions are passed via mempool.
postBlock []func(blockchainer.Blockchainer, *mempool.Pool, *block.Block)

sbCommittee keys.PublicKeys

log *zap.Logger
Expand Down Expand Up @@ -151,6 +157,10 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration, log *zap.L
cfg.MemPoolSize = defaultMemPoolSize
log.Info("mempool size is not set or wrong, setting default value", zap.Int("MemPoolSize", cfg.MemPoolSize))
}
if cfg.P2PSigExtensions && cfg.P2PNotaryRequestPayloadPoolSize <= 0 {
cfg.P2PNotaryRequestPayloadPoolSize = defaultP2PNotaryRequestPayloadPoolSize
log.Info("P2PNotaryRequestPayloadPool size is not set or wrong, setting default value", zap.Int("P2PNotaryRequestPayloadPoolSize", cfg.P2PNotaryRequestPayloadPoolSize))
}
if cfg.MaxTraceableBlocks == 0 {
cfg.MaxTraceableBlocks = defaultMaxTraceableBlocks
log.Info("MaxTraceableBlocks is not set or wrong, using default value", zap.Uint32("MaxTraceableBlocks", cfg.MaxTraceableBlocks))
Expand Down Expand Up @@ -720,6 +730,13 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
bc.lock.Unlock()
return fmt.Errorf("failed to call OnPersistEnd for Policy native contract: %w", err)
}
if bc.P2PSigExtensionsEnabled() {
err := bc.contracts.Notary.OnPersistEnd(bc.dao)
if err != nil {
bc.lock.Unlock()
return fmt.Errorf("failed to call OnPersistEnd for Notary native contract: %w", err)
}
}
if err := bc.contracts.Designate.OnPersistEnd(bc.dao); err != nil {
bc.lock.Unlock()
return err
Expand All @@ -734,6 +751,9 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
bc.topBlock.Store(block)
atomic.StoreUint32(&bc.blockHeight, block.Index)
bc.memPool.RemoveStale(func(tx *transaction.Transaction) bool { return bc.isTxStillRelevant(tx, txpool) }, bc)
for _, f := range bc.postBlock {
f(bc, txpool, block)
}
bc.lock.Unlock()

updateBlockHeightMetric(block.Index)
Expand Down Expand Up @@ -934,6 +954,16 @@ func (bc *Blockchain) GetGoverningTokenBalance(acc util.Uint160) (*big.Int, uint
return &neo.Balance, neo.LastUpdatedBlock
}

// GetDepositBalance returns Notary deposit amount for the specified account.
func (bc *Blockchain) GetDepositBalance(acc util.Uint160) *big.Int {
return bc.contracts.Notary.BalanceOf(bc.dao, acc)
}

// GetDepositExpiration returns Notary deposit expiration height for the specified account.
func (bc *Blockchain) GetDepositExpiration(acc util.Uint160) uint32 {
return bc.contracts.Notary.ExpirationOf(bc.dao, acc)
}

// LastBatch returns last persisted storage batch.
func (bc *Blockchain) LastBatch() *storage.MemBatch {
return bc.lastBatch
Expand Down Expand Up @@ -1204,16 +1234,6 @@ func (bc *Blockchain) FeePerByte() int64 {
return bc.contracts.Policy.GetFeePerByteInternal(bc.dao)
}

// GetMaxBlockSize returns maximum allowed block size from native Policy contract.
func (bc *Blockchain) GetMaxBlockSize() uint32 {
return bc.contracts.Policy.GetMaxBlockSizeInternal(bc.dao)
}

// GetMaxBlockSystemFee returns maximum block system fee from native Policy contract.
func (bc *Blockchain) GetMaxBlockSystemFee() int64 {
return bc.contracts.Policy.GetMaxBlockSystemFeeInternal(bc.dao)
}

// GetMemPool returns the memory pool of the blockchain.
func (bc *Blockchain) GetMemPool() *mempool.Pool {
return bc.memPool
Expand Down Expand Up @@ -1749,3 +1769,46 @@ func (bc *Blockchain) newInteropContext(trigger trigger.Type, d dao.DAO, block *
func (bc *Blockchain) P2PSigExtensionsEnabled() bool {
return bc.config.P2PSigExtensions
}

// RegisterPostBlock appends provided function to the list of functions which should be run after new block
// is stored.
func (bc *Blockchain) RegisterPostBlock(f func(blockchainer.Blockchainer, *mempool.Pool, *block.Block)) {
bc.postBlock = append(bc.postBlock, f)
}

// RunWithCurrentState allows to call f under the Blockchain lock.
func (bc *Blockchain) RunWithCurrentState(f func() error) error {
bc.lock.Lock()
defer bc.lock.Unlock()

return f()
}

// -- start Policer.

// GetPolicer provides access to policy values via Policer interface.
func (bc *Blockchain) GetPolicer() blockchainer.Policer {
return bc
}

// GetMaxBlockSize returns maximum allowed block size from native Policy contract.
func (bc *Blockchain) GetMaxBlockSize() uint32 {
return bc.contracts.Policy.GetMaxBlockSizeInternal(bc.dao)
}

// GetMaxBlockSystemFee returns maximum block system fee from native Policy contract.
func (bc *Blockchain) GetMaxBlockSystemFee() int64 {
return bc.contracts.Policy.GetMaxBlockSystemFeeInternal(bc.dao)
}

// GetMaxNotValidBeforeDelta returns the maximum NVBDelta Notary value.
func (bc *Blockchain) GetMaxNotValidBeforeDelta() uint32 {
return bc.contracts.Notary.GetMaxNotValidBeforeDelta(bc.dao)
}

// GetMaxVerificationGAS returns maximum verification GAS Policy limit.
func (bc *Blockchain) GetMaxVerificationGAS() int64 {
return bc.contracts.Policy.GetMaxVerificationGas(bc.dao)
}

// -- end Policer.
172 changes: 172 additions & 0 deletions pkg/core/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import (
"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/io"
"github.com/nspcc-dev/neo-go/pkg/network/notarypool"
"github.com/nspcc-dev/neo-go/pkg/network/payload"
"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"
Expand Down Expand Up @@ -1437,3 +1439,173 @@ func TestRemoveUntraceable(t *testing.T) {
require.NoError(t, err)
require.Len(t, b.Transactions, 0)
}

func TestVerifyAndPoolPayload(t *testing.T) {
// we need a proper Blockchainer for this test, so we have to keep it in core package
bc := newTestChain(t)
defer bc.Close()

var (
nonce uint32
notaryHash = bc.contracts.Notary.Hash
gasHash = bc.contracts.GAS.Hash
mp = notarypool.NewNotaryPool(10, bc)
)

signer, err := wallet.NewAccount()
require.NoError(t, err)

newTx := func(t *testing.T, sender util.Uint160) *transaction.Transaction {
tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.RET)}, 100)
tx.ValidUntilBlock = bc.BlockHeight() + bc.GetMaxNotValidBeforeDelta() + 10
nonce++
tx.Nonce = nonce
tx.Signers = []transaction.Signer{{
Account: sender,
Scopes: transaction.None,
}}
tx.Scripts = []transaction.Witness{
{}, // empty witness: for MainTx -> to be collected, for FallbackTx -> should be filled by Notary node.
}
return tx
}
newFallbackTx := func(t *testing.T, nvb uint32, sender util.Uint160) *transaction.Transaction {
tx := newTx(t, sender)
tx.Attributes = append(tx.Attributes, transaction.Attribute{
Type: transaction.NotValidBeforeT,
Value: &transaction.NotValidBefore{Height: nvb}})
tx.Signers = append(tx.Signers, transaction.Signer{
Account: signer.PrivateKey().PublicKey().GetScriptHash(),
Scopes: transaction.CalledByEntry,
})
signer.SignTx(tx)
return tx
}

// bad: NotaryRequest payload should be signed by notary service request sender (which is `signer`)
fallbackTx := newFallbackTx(t, bc.BlockHeight(), notaryHash)
requestPayload := &payload.P2PNotaryRequest{
MainTransaction: newTx(t, neoOwner),
FallbackTransaction: fallbackTx,
Network: bc.GetConfig().Magic,
}
requestPayload.Witness = transaction.Witness{
InvocationScript: testchain.SignCommittee(requestPayload.GetSignedPart()),
VerificationScript: testchain.CommitteeVerificationScript(),
}
require.Error(t, bc.RunWithCurrentState(func() error {
return mp.VerifyAndPoolPayload(bc, requestPayload)
}))

// bad: NotaryContract should be the sender of FallbackTx
fallbackTx = newFallbackTx(t, bc.BlockHeight(), neoOwner)
requestPayload = &payload.P2PNotaryRequest{
MainTransaction: newTx(t, neoOwner),
FallbackTransaction: fallbackTx,
Network: bc.GetConfig().Magic,
}
requestPayload.Witness = transaction.Witness{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, signer.PrivateKey().Sign(requestPayload.GetSignedPart())...),
VerificationScript: signer.PrivateKey().PublicKey().GetVerificationScript(),
}
require.Error(t, bc.RunWithCurrentState(func() error {
return mp.VerifyAndPoolPayload(bc, requestPayload)
}))

// bad: FallbackTx should have proper witness for notary service requester
fallbackTx = newFallbackTx(t, bc.BlockHeight(), notaryHash)
fallbackTx.Scripts = []transaction.Witness{
{}, // empty witness for Notary contract
{
InvocationScript: testchain.SignCommittee(fallbackTx.GetSignedPart()),
VerificationScript: testchain.CommitteeVerificationScript(),
},
}
requestPayload = &payload.P2PNotaryRequest{
MainTransaction: newTx(t, neoOwner),
FallbackTransaction: fallbackTx,
Network: bc.GetConfig().Magic,
}
requestPayload.Witness = transaction.Witness{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, signer.PrivateKey().Sign(requestPayload.GetSignedPart())...),
VerificationScript: signer.PrivateKey().PublicKey().GetVerificationScript(),
}
require.Error(t, bc.RunWithCurrentState(func() error {
return mp.VerifyAndPoolPayload(bc, requestPayload)
}))

// bad: FallbackTx.NotValidBefore > bc.BlockHeight + MaxNVBDelta
fallbackTx = newFallbackTx(t, bc.BlockHeight()+bc.GetMaxNotValidBeforeDelta()+1, notaryHash)
requestPayload = &payload.P2PNotaryRequest{
MainTransaction: newTx(t, neoOwner),
FallbackTransaction: fallbackTx,
Network: bc.GetConfig().Magic,
}
requestPayload.Witness = transaction.Witness{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, signer.PrivateKey().Sign(requestPayload.GetSignedPart())...),
VerificationScript: signer.PrivateKey().PublicKey().GetVerificationScript(),
}
require.Error(t, bc.RunWithCurrentState(func() error {
return mp.VerifyAndPoolPayload(bc, requestPayload)
}))

// bad: payload should be valid less than MaxNVBDelta after fallback transaction becomes valid
mainTx := newTx(t, neoOwner)
fallbackTx = newFallbackTx(t, mainTx.ValidUntilBlock-bc.GetMaxNotValidBeforeDelta()-1, notaryHash)
requestPayload = &payload.P2PNotaryRequest{
MainTransaction: mainTx,
FallbackTransaction: fallbackTx,
Network: bc.GetConfig().Magic,
}
requestPayload.Witness = transaction.Witness{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, signer.PrivateKey().Sign(requestPayload.GetSignedPart())...),
VerificationScript: signer.PrivateKey().PublicKey().GetVerificationScript(),
}
require.Error(t, bc.RunWithCurrentState(func() error {
return mp.VerifyAndPoolPayload(bc, requestPayload)
}))

// deposit 10.0 GAS for signer
mainTx = newTx(t, neoOwner) // need for the next test
transferTx := transferTokenFromMultisigAccount(t, bc, notaryHash, gasHash, 100*transaction.NotaryServiceFeePerKey, signer.PrivateKey().PublicKey().GetScriptHash(), int64(mainTx.ValidUntilBlock-1))
res, err := bc.GetAppExecResults(transferTx.Hash(), trigger.Application)
require.NoError(t, err)
require.Equal(t, vm.HaltState, res[0].VMState)

// bad: fallback transaction is valid after deposit expiration
fallbackTx = newFallbackTx(t, mainTx.ValidUntilBlock-bc.GetMaxNotValidBeforeDelta()+1, notaryHash)
requestPayload = &payload.P2PNotaryRequest{
MainTransaction: mainTx,
FallbackTransaction: fallbackTx,
Network: bc.GetConfig().Magic,
}
requestPayload.Witness = transaction.Witness{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, signer.PrivateKey().Sign(requestPayload.GetSignedPart())...),
VerificationScript: signer.PrivateKey().PublicKey().GetVerificationScript(),
}
require.Error(t, bc.RunWithCurrentState(func() error {
return mp.VerifyAndPoolPayload(bc, requestPayload)
}))

// deposit some more GAS to update deposit's `till`
mainTx = newTx(t, neoOwner) // need for the next test
transferTx = transferTokenFromMultisigAccount(t, bc, notaryHash, gasHash, 100*transaction.NotaryServiceFeePerKey, signer.PrivateKey().PublicKey().GetScriptHash(), int64(mainTx.ValidUntilBlock+2))
res, err = bc.GetAppExecResults(transferTx.Hash(), trigger.Application)
require.NoError(t, err)
require.Equal(t, vm.HaltState, res[0].VMState)

// good
fallbackTx = newFallbackTx(t, mainTx.ValidUntilBlock-bc.GetMaxNotValidBeforeDelta()+1, notaryHash)
requestPayload = &payload.P2PNotaryRequest{
MainTransaction: mainTx,
FallbackTransaction: fallbackTx,
Network: bc.GetConfig().Magic,
}
requestPayload.Witness = transaction.Witness{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, signer.PrivateKey().Sign(requestPayload.GetSignedPart())...),
VerificationScript: signer.PrivateKey().PublicKey().GetVerificationScript(),
}
require.NoError(t, bc.RunWithCurrentState(func() error {
return mp.VerifyAndPoolPayload(bc, requestPayload)
}))
}
Loading

0 comments on commit 9cbf6a0

Please sign in to comment.