Merge pull request #724 from nspcc-dev/feature/submitblock

rpc: implement submitblock
This commit is contained in:
Roman Khimov 2020-03-06 12:08:45 +03:00 committed by GitHub
commit f8eee778f4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 129 additions and 23 deletions

View file

@ -63,7 +63,7 @@ which would yield the response:
| `invokefunction` | Yes | | `invokefunction` | Yes |
| `invokescript` | Yes | | `invokescript` | Yes |
| `sendrawtransaction` | Yes | | `sendrawtransaction` | Yes |
| `submitblock` | No (#344) | | `submitblock` | Yes |
| `validateaddress` | Yes | | `validateaddress` | Yes |
### Unsupported methods ### Unsupported methods

View file

@ -52,6 +52,9 @@ var (
// ErrPolicy is returned on attempt to add transaction that doesn't // ErrPolicy is returned on attempt to add transaction that doesn't
// comply with node's configured policy into the mempool. // comply with node's configured policy into the mempool.
ErrPolicy = errors.New("not allowed by policy") ErrPolicy = errors.New("not allowed by policy")
// ErrInvalidBlockIndex is returned when trying to add block with index
// other than expected height of the blockchain.
ErrInvalidBlockIndex error = errors.New("invalid block index")
) )
var ( var (
genAmount = []int{8, 7, 6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} genAmount = []int{8, 7, 6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
@ -307,7 +310,7 @@ func (bc *Blockchain) AddBlock(block *block.Block) error {
expectedHeight := bc.BlockHeight() + 1 expectedHeight := bc.BlockHeight() + 1
if expectedHeight != block.Index { if expectedHeight != block.Index {
return fmt.Errorf("expected block %d, but passed block %d", expectedHeight, block.Index) return ErrInvalidBlockIndex
} }
headerLen := bc.headerListLen() headerLen := bc.headerListLen()

View file

@ -391,6 +391,17 @@ func TestCreateBasicChain(t *testing.T) {
require.NoError(t, writer.Err) require.NoError(t, writer.Err)
} }
} }
// Blocks for `submitblock` test. If you are planning to modify test chain from `testblocks.acc`,
// please, update params value of `empty block` and `positive` tests.
var blocks []*block.Block
blocks = append(blocks, bc.newBlock(), bc.newBlock(newMinerTX()))
for _, b := range blocks {
buf := io.NewBufBinWriter()
b.EncodeBinary(buf.BinWriter)
require.NoError(t, buf.Err)
t.Log(hex.EncodeToString(buf.Bytes()))
}
} }
func newNEP5Transfer(sc, from, to util.Uint160, amount int64) *transaction.Transaction { func newNEP5Transfer(sc, from, to util.Uint160, amount int64) *transaction.Transaction {

View file

@ -20,6 +20,18 @@ type (
var ( var (
// ErrInvalidParams represents a generic 'invalid parameters' error. // ErrInvalidParams represents a generic 'invalid parameters' error.
ErrInvalidParams = NewInvalidParamsError("", nil) ErrInvalidParams = NewInvalidParamsError("", nil)
// ErrAlreadyExists represents SubmitError with code -501
ErrAlreadyExists = NewSubmitError(-501, "Block or transaction already exists and cannot be sent repeatedly.")
// ErrOutOfMemory represents SubmitError with code -502
ErrOutOfMemory = NewSubmitError(-502, "The memory pool is full and no more transactions can be sent.")
// ErrUnableToVerify represents SubmitError with code -503
ErrUnableToVerify = NewSubmitError(-503, "The block cannot be validated.")
// ErrValidationFailed represents SubmitError with code -504
ErrValidationFailed = NewSubmitError(-504, "Block or transaction validation failed.")
// ErrPolicyFail represents SubmitError with code -505
ErrPolicyFail = NewSubmitError(-505, "One of the Policy filters failed.")
// ErrUnknown represents SubmitError with code -500
ErrUnknown = NewSubmitError(-500, "Unknown error.")
) )
// NewError is an Error constructor that takes Error contents from its // NewError is an Error constructor that takes Error contents from its
@ -71,6 +83,12 @@ func NewRPCError(message string, data string, cause error) *Error {
return NewError(-100, http.StatusUnprocessableEntity, message, data, cause) return NewError(-100, http.StatusUnprocessableEntity, message, data, cause)
} }
// NewSubmitError creates a new error with
// specified error code and error message
func NewSubmitError(code int64, message string) *Error {
return NewError(code, http.StatusUnprocessableEntity, message, "", nil)
}
// Error implements the error interface. // Error implements the error interface.
func (e *Error) Error() string { func (e *Error) Error() string {
return fmt.Sprintf("%s (%d) - %s - %s", e.Message, e.Code, e.Data, e.Cause) return fmt.Sprintf("%s (%d) - %s - %s", e.Message, e.Code, e.Data, e.Cause)

View file

@ -187,6 +187,14 @@ var (
}, },
) )
submitblockCalled = prometheus.NewCounter(
prometheus.CounterOpts{
Help: "Number of calls to submitblock rpc endpoint",
Name: "submitblock_called",
Namespace: "neogo",
},
)
getstorageCalled = prometheus.NewCounter( getstorageCalled = prometheus.NewCounter(
prometheus.CounterOpts{ prometheus.CounterOpts{
Help: "Number of calls to getstorage rpc endpoint", Help: "Number of calls to getstorage rpc endpoint",
@ -218,6 +226,7 @@ func init() {
gettxoutCalled, gettxoutCalled,
getrawtransactionCalled, getrawtransactionCalled,
sendrawtransactionCalled, sendrawtransactionCalled,
submitblockCalled,
getstorageCalled, getstorageCalled,
) )
} }

View file

@ -12,6 +12,7 @@ import (
"github.com/nspcc-dev/neo-go/config" "github.com/nspcc-dev/neo-go/config"
"github.com/nspcc-dev/neo-go/pkg/core" "github.com/nspcc-dev/neo-go/pkg/core"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/hash"
@ -306,6 +307,10 @@ Methods:
case "invokescript": case "invokescript":
results, resultsErr = s.invokescript(reqParams) results, resultsErr = s.invokescript(reqParams)
case "submitblock":
submitblockCalled.Inc()
results, resultsErr = s.submitBlock(reqParams)
case "sendrawtransaction": case "sendrawtransaction":
sendrawtransactionCalled.Inc() sendrawtransactionCalled.Inc()
results, resultsErr = s.sendrawtransaction(reqParams) results, resultsErr = s.sendrawtransaction(reqParams)
@ -831,6 +836,34 @@ func (s *Server) runScriptInVM(script []byte) *result.Invoke {
return result return result
} }
// submitBlock broadcasts a raw block over the NEO network.
func (s *Server) submitBlock(reqParams request.Params) (interface{}, error) {
param, ok := reqParams.ValueWithType(0, request.StringT)
if !ok {
return nil, response.ErrInvalidParams
}
blockBytes, err := param.GetBytesHex()
if err != nil {
return nil, response.ErrInvalidParams
}
b := block.Block{}
r := io.NewBinReaderFromBuf(blockBytes)
b.DecodeBinary(r)
if r.Err != nil {
return nil, response.ErrInvalidParams
}
err = s.chain.AddBlock(&b)
if err != nil {
switch err {
case core.ErrInvalidBlockIndex, core.ErrAlreadyExists:
return nil, response.ErrAlreadyExists
default:
return nil, response.ErrValidationFailed
}
}
return true, nil
}
func (s *Server) sendrawtransaction(reqParams request.Params) (interface{}, error) { func (s *Server) sendrawtransaction(reqParams request.Params) (interface{}, error) {
var resultsErr error var resultsErr error
var results interface{} var results interface{}
@ -844,28 +877,24 @@ func (s *Server) sendrawtransaction(reqParams request.Params) (interface{}, erro
tx := &transaction.Transaction{} tx := &transaction.Transaction{}
tx.DecodeBinary(r) tx.DecodeBinary(r)
if r.Err != nil { if r.Err != nil {
err = errors.Wrap(r.Err, "transaction DecodeBinary failed") return nil, response.ErrInvalidParams
} else { }
relayReason := s.coreServer.RelayTxn(tx) relayReason := s.coreServer.RelayTxn(tx)
switch relayReason { switch relayReason {
case network.RelaySucceed: case network.RelaySucceed:
results = true results = true
case network.RelayAlreadyExists: case network.RelayAlreadyExists:
err = errors.New("block or transaction already exists and cannot be sent repeatedly") resultsErr = response.ErrAlreadyExists
case network.RelayOutOfMemory: case network.RelayOutOfMemory:
err = errors.New("the memory pool is full and no more transactions can be sent") resultsErr = response.ErrOutOfMemory
case network.RelayUnableToVerify: case network.RelayUnableToVerify:
err = errors.New("the block cannot be validated") resultsErr = response.ErrUnableToVerify
case network.RelayInvalid: case network.RelayInvalid:
err = errors.New("block or transaction validation failed") resultsErr = response.ErrValidationFailed
case network.RelayPolicyFail: case network.RelayPolicyFail:
err = errors.New("one of the Policy filters failed") resultsErr = response.ErrPolicyFail
default: default:
err = errors.New("unknown error") resultsErr = response.ErrUnknown
}
}
if err != nil {
resultsErr = response.NewInternalServerError(err.Error(), err)
} }
} }

View file

@ -767,6 +767,42 @@ var rpcTestCases = map[string][]rpcTestCase{
fail: true, fail: true,
}, },
}, },
"submitblock": {
{
name: "empty block",
params: `["00000000270dd14a8ddb4961cada75e9ec4cce906f9feeaef21b1d6cea5b2f0230baf92e00000000000000000000000000000000000000000000000000000000000000009f0e625ed10000005704000000000000be48d3a3f5d10013ab9ffee489706078714f1ea201fd040140bd2b961ca7df75d6d8896fd4ef487128ddd1ca9feda9cb09da58a6a24607fc71cd572ba072e4ec9d9847c7da6a33fc44dc9a64208fd62851d27d330012bd0bf640ee6e92653789b0d1c87e0cd485c5bbd84194479995930567df9ffec7ab370c965df320134632e53d139a785840cecb16c4fe801f3c6bed17a9dd83460f5f297440fbe5f9d3cc23f9c7b9f10e191eae541fcb71eb03799c09886aa04c0bc43e98c2c91686a5b4fb988c8855f4d217807f01ca9a61bc0d5536eb3149dce7ae66965c407af69085454fb39f0c27cf308219c09efbe902f0a092a065b38864c97529f1dd12fa54c346e8d9482106c7154b3170c150887170c78fe78a5c70e3956880489c8b532102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd622102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc22103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee69954ae00"]`,
fail: true,
},
{
name: "invalid block height",
params: `["000000005fb86f62eafe8e9246bc0d1648e4e5c8389dee9fb7fe03fcc6772ec8c5e4ec2aedb908054ac1409be5f77d5369c6e03490b2f6676d68d0b3370f8159e0fdadf99bc05f5e030000005704000000000000be48d3a3f5d10013ab9ffee489706078714f1ea201fd0401406f299c82b513f59f5bd120317974852c9694c6e10db1ef2f1bb848b1a33e47a08f8dc03ee784166b2060a94cd4e7af88899b39787938f7f2763ea4d2182776ed40f3bafd85214fef38a4836ca97793001ea411f553c51e88781f7b916c59c145bff28314b6e7ea246789422a996fc4937e290a1b40f6b97c5222540f65b0d47aca40d2b3d19203d456428bfdb529e846285052105957385b65388b9a617f6e2d56a64ec41aa73439eafccb52987bb1975c9b67518b053d9e61b445e4a3377dbc206640bd688489bd62adf6bed9d61a73905b9591eb87053c6f0f4dd70f3bee7295541b490caef044b55b6f9f01dc4a05a756a3f2edd06f5adcbe4e984c1e552f9023f08b532102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd622102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc22103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee69954ae0100000000000000000000"]`,
fail: true,
},
{
name: "invalid hex",
params: `["000000005gb86f62eafe8e9246bc0d1648e4e5c8389dee9fb7fe03fcc6772ec8c5e4ec2aedb908054ac1409be5f77d5369c6e03490b2f6676d68d0b3370f8159e0fdadf99bc05f5e030000005704000000000000be48d3a3f5d10013ab9ffee489706078714f1ea201fd0401406f299c82b513f59f5bd120317974852c9694c6e10db1ef2f1bb848b1a33e47a08f8dc03ee784166b2060a94cd4e7af88899b39787938f7f2763ea4d2182776ed40f3bafd85214fef38a4836ca97793001ea411f553c51e88781f7b916c59c145bff28314b6e7ea246789422a996fc4937e290a1b40f6b97c5222540f65b0d47aca40d2b3d19203d456428bfdb529e846285052105957385b65388b9a617f6e2d56a64ec41aa73439eafccb52987bb1975c9b67518b053d9e61b445e4a3377dbc206640bd688489bd62adf6bed9d61a73905b9591eb87053c6f0f4dd70f3bee7295541b490caef044b55b6f9f01dc4a05a756a3f2edd06f5adcbe4e984c1e552f9023f08b532102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd622102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc22103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee69954ae0100000000000000000000"]`,
fail: true,
},
{
name: "invalid block bytes",
params: `["0000000027"]`,
fail: true,
},
{
name: "no params",
params: `[]`,
fail: true,
},
{
name: "positive",
// If you are planning to modify test chain from `testblocks.acc`, please, update param value
params: `["00000000270dd14a8ddb4961cada75e9ec4cce906f9feeaef21b1d6cea5b2f0230baf92eedb908054ac1409be5f77d5369c6e03490b2f6676d68d0b3370f8159e0fdadf9b93a615ed10000005704000000000000be48d3a3f5d10013ab9ffee489706078714f1ea201fd0401402dca64bfc20107819171fab7c636427e9634a58f560ff9ef94ff9005a82509dd5e1b054d195201013a8477529d023d794a3ca6efa717f5d7e541e3b81fbabff840b5395429f0da5a8d1023907dca3771c02eca31e520e1acc2851b373a6aedd5bf6ba75a80b0949ee0794156ae48356b98e761143b90ebaa816ca4b0b0f91b38ab40a6feb77e09c0c95c6f2c6571155843f388e1f7cb7ba5af2bc235bac1d57a8c316d817e54895bfdb98b9c34ed9550e7ccff2a06b46ca0f2b99ba720368d9934eb40dbbdc94154cc304296ccda73e4e0257cdc344c4bad4c70f419f59f5d6d862819cead91c55e73e7c30daff2cd38d74eae3e0e49a5226de3060843d0e3b9262dc08b532102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd622102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc22103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee69954ae0100000000000000000000"]`,
result: func(e *executor) interface{} {
v := true
return &v
},
},
},
"validateaddress": { "validateaddress": {
{ {
name: "positive", name: "positive",