Implemented rpc server method GetRawTransaction (#135)

* Added utility function GetVarSize

* 1) Added Size method: this implied that Fixed8 implements now the serializable interface. 2) Added few arithmetic operation (Add, Sub, div): this will be used to calculated networkfeeand feePerByte. Changed return value of the Value() method to int instead of int64. Modified fixed8_test accordingly.

* Implemented Size or MarshalJSON method.
- Structs accepting the Size method implement the serializable interface.
- Structs accepting the MarshalJSON method implements the customized json marshaller interface.

* Added fee calculation

* Implemented rcp server method GetRawTransaction

* Updated Tests

* Fixed:
1) NewFixed8 will accept as input int64
2) race condition affecting configDeafault, blockchainDefault

* Simplified Size calculation

* 1) Removed global variable blockchainDefault, configDefault
2) Extended Blockchainer interface to include the methods: References, FeePerByte, SystemFee, NetworkFee
3) Deleted fee_test.go, fee.go. Moved corresponding methods to blockchain_test.go and blockchain.go respectively
4) Amended tx_raw_output.go

* Simplified GetVarSize Method

* Replaced ValueAtAndType with ValueWithType

* Cosmetic changes + Added test case getrawtransaction_7

* Clean up Print statement

* Filled up keys

* Aligned verbose logic with the C#-neo implementation

* Implemented @Kim requests
Refactor server_test.go

* Small fixes

* Fixed verbose logic
Added more tests
Cosmetic changes

* Replaced assert.NoError with require.NoError

* Fixed tests by adding context.Background() as argument

* Fixed tests
This commit is contained in:
dauTT 2019-02-20 18:39:32 +01:00 committed by fabwa
parent 20bb05b335
commit 19201dcf52
23 changed files with 859 additions and 151 deletions

View file

@ -6,6 +6,8 @@ import (
"os"
"time"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/go-yaml/yaml"
"github.com/pkg/errors"
)
@ -118,3 +120,19 @@ func Load(path string, netMode NetMode) (Config, error) {
return config, nil
}
// TryGetValue returns the system fee base on transaction type.
func (s SystemFee) TryGetValue(txType transaction.TXType) util.Fixed8 {
switch txType {
case transaction.EnrollmentType:
return util.NewFixed8(s.EnrollmentTransaction)
case transaction.IssueType:
return util.NewFixed8(s.IssueTransaction)
case transaction.PublishType:
return util.NewFixed8(s.PublishTransaction)
case transaction.RegisterType:
return util.NewFixed8(s.RegisterTransaction)
default:
return util.NewFixed8(0)
}
}

View file

@ -70,9 +70,9 @@ func NewBlockchain(ctx context.Context, s storage.Store, cfg config.ProtocolConf
blockCache: NewCache(),
verifyBlocks: false,
}
go bc.run(ctx)
if err := bc.init(); err != nil {
go bc.run(ctx)
if err := bc.init(); err != nil {
return nil, err
}
@ -150,7 +150,7 @@ func (bc *Blockchain) init() error {
headers := make([]*Header, 0)
for hash != targetHash {
header, err := bc.getHeader(hash)
header, err := bc.GetHeader(hash)
if err != nil {
return fmt.Errorf("could not get header %s: %s", hash, err)
}
@ -494,7 +494,7 @@ func (bc *Blockchain) GetBlock(hash util.Uint256) (*Block, error) {
return block, nil
}
func (bc *Blockchain) getHeader(hash util.Uint256) (*Header, error) {
func (bc *Blockchain) GetHeader(hash util.Uint256) (*Header, error) {
b, err := bc.Get(storage.AppendPrefix(storage.DataBlock, hash.BytesReverse()))
if err != nil {
return nil, err
@ -515,7 +515,7 @@ func (bc *Blockchain) HasTransaction(hash util.Uint256) bool {
// HasBlock return true if the blockchain contains the given
// block hash.
func (bc *Blockchain) HasBlock(hash util.Uint256) bool {
if header, err := bc.getHeader(hash); err == nil {
if header, err := bc.GetHeader(hash); err == nil {
return header.Index <= bc.BlockHeight()
}
return false
@ -585,6 +585,62 @@ func (bc *Blockchain) GetAccountState(scriptHash util.Uint160) *AccountState {
return as
}
// GetConfig returns the config stored in the blockchain
func (bc *Blockchain) GetConfig() config.ProtocolConfiguration {
return bc.config
}
// References returns a map with input prevHash as key (util.Uint256)
// and transaction output as value from a transaction t.
// @TODO: unfortunately we couldn't attach this method to the Transaction struct in the
// transaction package because of a import cycle problem. Perhaps we should think to re-design
// the code base to avoid this situation.
func (bc *Blockchain) References(t *transaction.Transaction) map[util.Uint256]*transaction.Output {
references := make(map[util.Uint256]*transaction.Output)
for prevHash, inputs := range t.GroupInputsByPrevHash() {
if tx, _, err := bc.GetTransaction(prevHash); err != nil {
tx = nil
} else if tx != nil {
for _, in := range inputs {
references[in.PrevHash] = tx.Outputs[in.PrevIndex]
}
} else {
references = nil
}
}
return references
}
// FeePerByte returns network fee divided by the size of the transaction
func (bc *Blockchain) FeePerByte(t *transaction.Transaction) util.Fixed8 {
return bc.NetworkFee(t).Div(int64(t.Size()))
}
// NetworkFee returns network fee
func (bc *Blockchain) NetworkFee(t *transaction.Transaction) util.Fixed8 {
inputAmount := util.NewFixed8(0)
for _, txOutput := range bc.References(t) {
if txOutput.AssetID == utilityTokenTX().Hash() {
inputAmount.Add(txOutput.Amount)
}
}
outputAmount := util.NewFixed8(0)
for _, txOutput := range t.Outputs {
if txOutput.AssetID == utilityTokenTX().Hash() {
outputAmount.Add(txOutput.Amount)
}
}
return inputAmount.Sub(outputAmount).Sub(bc.SystemFee(t))
}
// SystemFee returns system fee
func (bc *Blockchain) SystemFee(t *transaction.Transaction) util.Fixed8 {
return bc.GetConfig().SystemFee.TryGetValue(t.Type)
}
func hashAndIndexToBytes(h util.Uint256, index uint32) []byte {
buf := make([]byte, 4)
binary.LittleEndian.PutUint32(buf, index)

View file

@ -6,7 +6,10 @@ import (
"github.com/CityOfZion/neo-go/config"
"github.com/CityOfZion/neo-go/pkg/core/storage"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAddHeaders(t *testing.T) {
@ -78,16 +81,14 @@ func TestGetHeader(t *testing.T) {
assert.Nil(t, err)
hash := block.Hash()
header, err := bc.getHeader(hash)
if err != nil {
t.Fatal(err)
}
header, err := bc.GetHeader(hash)
require.NoError(t, err)
assert.Equal(t, block.Header(), header)
block = newBlock(2)
hash = block.Hash()
_, err = bc.getHeader(hash)
assert.NotNil(t, err)
_, err = bc.GetHeader(block.Hash())
assert.Error(t, err)
}
func TestGetBlock(t *testing.T) {
@ -155,3 +156,40 @@ func newTestChain(t *testing.T) *Blockchain {
}
return chain
}
func TestSize(t *testing.T) {
txID := "f999c36145a41306c846ea80290416143e8e856559818065be3f4e143c60e43a"
tx := getTestTransaction(txID, t)
assert.Equal(t, 283, tx.Size())
assert.Equal(t, 22, util.GetVarSize(tx.Attributes))
assert.Equal(t, 35, util.GetVarSize(tx.Inputs))
assert.Equal(t, 121, util.GetVarSize(tx.Outputs))
assert.Equal(t, 103, util.GetVarSize(tx.Scripts))
}
func getTestBlockchain(t *testing.T) *Blockchain {
net := config.ModeUnitTestNet
configPath := "../../config"
cfg, err := config.Load(configPath, net)
require.NoError(t, err, "could not create levelDB chain")
// adjust datadirectory to point to the correct folder
cfg.ApplicationConfiguration.DataDirectoryPath = "../rpc/chains/unit_testnet"
chain, err := NewBlockchainLevelDB(context.Background(), cfg)
require.NoErrorf(t, err, "could not create levelDB chain")
return chain
}
func getTestTransaction(txID string, t *testing.T) *transaction.Transaction {
chain := getTestBlockchain(t)
txHash, err := util.Uint256DecodeString(txID)
require.NoErrorf(t, err, "could not decode string %s to Uint256", txID)
tx, _, err := chain.GetTransaction(txHash)
require.NoErrorf(t, err, "could not get transaction with hash=%s", txHash)
return tx
}

View file

@ -1,22 +1,31 @@
package core
import (
"github.com/CityOfZion/neo-go/config"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/util"
)
// Blockchainer is an interface that abstract the implementation
// of the blockchain.
type Blockchainer interface {
GetConfig() config.ProtocolConfiguration
AddHeaders(...*Header) error
AddBlock(*Block) error
BlockHeight() uint32
HeaderHeight() uint32
GetBlock(hash util.Uint256) (*Block, error)
GetHeaderHash(int) util.Uint256
GetHeader(hash util.Uint256) (*Header, error)
CurrentHeaderHash() util.Uint256
CurrentBlockHash() util.Uint256
HasBlock(util.Uint256) bool
HasTransaction(util.Uint256) bool
GetAssetState(util.Uint256) *AssetState
GetAccountState(util.Uint160) *AccountState
GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error)
References(t *transaction.Transaction) map[util.Uint256]*transaction.Output
FeePerByte(t *transaction.Transaction) util.Fixed8
SystemFee(t *transaction.Transaction) util.Fixed8
NetworkFee(t *transaction.Transaction) util.Fixed8
}

View file

@ -1,5 +1,51 @@
package transaction
var (
attrLookup = map[AttrUsage]string{
ContractHash: "ContractHash",
ECDH02: "ECDH02",
ECDH03: "ECDH03",
Script: "Script",
Vote: "Vote",
CertURL: "CertURL",
DescriptionURL: "DescriptionURL",
Description: "Description",
Hash1: "Hash1",
Hash2: "Hash2",
Hash3: "Hash3",
Hash4: "Hash4",
Hash5: "Hash5",
Hash6: "Hash6",
Hash7: "Hash7",
Hash8: "Hash8",
Hash9: "Hash9",
Hash10: "Hash10",
Hash11: "Hash11",
Hash12: "Hash12",
Hash13: "Hash13",
Hash14: "Hash14",
Hash15: "Hash15",
Remark: "Remark",
Remark1: "Remark1",
Remark2: "Remark2",
Remark3: "Remark3",
Remark4: "Remark4",
Remark5: "Remark5",
Remark6: "Remark6",
Remark7: "Remark7",
Remark8: "Remark8",
Remark9: "Remark9",
Remark10: "Remark10",
Remark11: "Remark11",
Remark12: "Remark12",
Remark13: "Remark13",
Remark14: "Remark14",
Remark15: "Remark15",
}
)
// AttrUsage represents the purpose of the attribute.
type AttrUsage uint8
@ -47,3 +93,11 @@ const (
Remark14 AttrUsage = 0xfe
Remark15 AttrUsage = 0xff
)
// String implements the stringer interface.
func (attr AttrUsage) String() string {
if v, ok := attrLookup[attr]; ok {
return v
}
return "Unkown Attribute"
}

View file

@ -2,6 +2,8 @@ package transaction
import (
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"io"
@ -77,3 +79,26 @@ func (attr *Attribute) EncodeBinary(w io.Writer) error {
}
return fmt.Errorf("failed encoding TX attribute usage: 0x%2x", attr.Usage)
}
// Size returns the size in number bytes of the Attribute
func (attr *Attribute) Size() int {
switch attr.Usage {
case ContractHash, ECDH02, ECDH03, Vote,
Hash1, Hash2, Hash3, Hash4, Hash5, Hash6, Hash7, Hash8, Hash9, Hash10, Hash11, Hash12, Hash13, Hash14, Hash15:
return 33 // uint8 + 32 = size(attrUsage) + 32
case Script:
return 21 // uint8 + 20 = size(attrUsage) + 20
case Description:
return 2 + len(attr.Data) // uint8 + uint8+ len of data = size(attrUsage) + size(byte) + len of data
default:
return 1 + len(attr.Data) // uint8 + len of data = size(attrUsage) + len of data
}
}
// MarshalJSON implements the json Marschaller interface
func (attr *Attribute) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"usage": attr.Usage.String(),
"data": hex.EncodeToString(attr.Data),
})
}

View file

@ -7,13 +7,13 @@ import (
"github.com/CityOfZion/neo-go/pkg/util"
)
// Input represents a Transaction input.
// Input represents a Transaction input (CoinReference).
type Input struct {
// The hash of the previous transaction.
PrevHash util.Uint256
PrevHash util.Uint256 `json:"txid"`
// The index of the previous transaction.
PrevIndex uint16
PrevIndex uint16 `json:"vout"`
}
// DecodeBinary implements the Payload interface.
@ -34,3 +34,8 @@ func (in *Input) EncodeBinary(w io.Writer) error {
}
return nil
}
// Size returns the size in bytes of the Input
func (in *Input) Size() int {
return in.PrevHash.Size() + 2 // 2 = sizeOf uint16
}

View file

@ -2,8 +2,10 @@ package transaction
import (
"encoding/binary"
"encoding/json"
"io"
"github.com/CityOfZion/neo-go/pkg/crypto"
"github.com/CityOfZion/neo-go/pkg/util"
)
@ -17,6 +19,10 @@ type Output struct {
// The address of the recipient.
ScriptHash util.Uint160
// The position of the Output in slice []Output. This is actually set in NewTransactionOutputRaw
// and used for diplaying purposes.
Position int
}
// NewOutput returns a new transaction output.
@ -49,3 +55,18 @@ func (out *Output) EncodeBinary(w io.Writer) error {
}
return binary.Write(w, binary.LittleEndian, out.ScriptHash)
}
// Size returns the size in bytes of the Output
func (out *Output) Size() int {
return out.AssetID.Size() + out.Amount.Size() + out.ScriptHash.Size()
}
// MarshalJSON implements the Marshaler interface
func (out *Output) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"asset": out.AssetID,
"value": out.Amount,
"address": crypto.AddressFromUint160(out.ScriptHash),
"n": out.Position,
})
}

View file

@ -250,3 +250,24 @@ func (t *Transaction) GroupInputsByPrevHash() map[util.Uint256][]*Input {
}
return m
}
// Size returns the size of the transaction in term of bytes
func (t *Transaction) Size() int {
attrSize := util.GetVarSize(t.Attributes)
inputSize := util.GetVarSize(t.Inputs)
outputSize := util.GetVarSize(t.Outputs)
witnesSize := util.GetVarSize(t.Scripts)
// uint8 + uint8 + attrSize + inputSize + outputSize + witnesSize
return 2 + attrSize + inputSize + outputSize + witnesSize
}
// Bytes convert the transaction to []byte
func (t *Transaction) Bytes() []byte {
buf := new(bytes.Buffer)
if err := t.EncodeBinary(buf); err != nil {
return nil
}
return buf.Bytes()
}

View file

@ -46,3 +46,8 @@ func (t TXType) String() string {
return "UnknownTransaction"
}
}
// MarshalJSON implements the json marshaller interface.
func (t TXType) MarshalJSON() ([]byte, error) {
return []byte(`"` + t.String() + `"`), nil
}

View file

@ -50,3 +50,8 @@ func (w *Witness) MarshalJSON() ([]byte, error) {
return json.Marshal(data)
}
// Size returns the size in bytes of the Witness.
func (w *Witness) Size() int {
return util.GetVarSize(w.InvocationScript) + util.GetVarSize(w.VerificationScript)
}

View file

@ -170,7 +170,7 @@ func calculateUtilityAmount() util.Fixed8 {
for i := 0; i < len(genAmount); i++ {
sum += genAmount[i]
}
return util.NewFixed8(sum * decrementInterval)
return util.NewFixed8(int64(sum * decrementInterval))
}
// headerSliceReverse reverses the given slice of *Header.

10
pkg/io/serializable.go Normal file
View file

@ -0,0 +1,10 @@
package io
import "io"
// Serializable defines the binary encoding/decoding interface.
type Serializable interface {
Size() int
DecodeBinary(io.Reader) error
EncodeBinary(io.Writer) error
}

View file

@ -4,18 +4,40 @@ import (
"testing"
"time"
"github.com/CityOfZion/neo-go/config"
"github.com/CityOfZion/neo-go/pkg/core"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/network/payload"
"github.com/CityOfZion/neo-go/pkg/util"
)
type testChain struct{}
func (chain testChain) GetConfig() config.ProtocolConfiguration {
panic("TODO")
}
func (chain testChain) References(t *transaction.Transaction) map[util.Uint256]*transaction.Output {
panic("TODO")
}
func (chain testChain) FeePerByte(t *transaction.Transaction) util.Fixed8 {
panic("TODO")
}
func (chain testChain) SystemFee(t *transaction.Transaction) util.Fixed8 {
panic("TODO")
}
func (chain testChain) NetworkFee(t *transaction.Transaction) util.Fixed8 {
panic("TODO")
}
func (chain testChain) AddHeaders(...*core.Header) error {
return nil
panic("TODO")
}
func (chain testChain) AddBlock(*core.Block) error {
return nil
panic("TODO")
}
func (chain testChain) BlockHeight() uint32 {
return 0
@ -24,16 +46,20 @@ func (chain testChain) HeaderHeight() uint32 {
return 0
}
func (chain testChain) GetBlock(hash util.Uint256) (*core.Block, error) {
return nil, nil
panic("TODO")
}
func (chain testChain) GetHeaderHash(int) util.Uint256 {
return util.Uint256{}
}
func (chain testChain) GetHeader(hash util.Uint256) (*core.Header, error) {
panic("TODO")
}
func (chain testChain) GetAssetState(util.Uint256) *core.AssetState {
return nil
panic("TODO")
}
func (chain testChain) GetAccountState(util.Uint160) *core.AccountState {
return nil
panic("TODO")
}
func (chain testChain) CurrentHeaderHash() util.Uint256 {
return util.Uint256{}
@ -47,6 +73,9 @@ func (chain testChain) HasBlock(util.Uint256) bool {
func (chain testChain) HasTransaction(util.Uint256) bool {
return false
}
func (chain testChain) GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error) {
panic("TODO")
}
type testDiscovery struct{}

View file

@ -2,6 +2,7 @@ package rpc
import (
"context"
"encoding/hex"
"fmt"
"net/http"
"strconv"
@ -182,7 +183,8 @@ Methods:
results = peers
case "getblocksysfee", "getcontractstate", "getrawmempool", "getrawtransaction", "getstorage", "submitblock", "gettxout", "invoke", "invokefunction", "invokescript", "sendrawtransaction":
case "getblocksysfee", "getcontractstate", "getrawmempool", "getstorage", "submitblock", "gettxout", "invoke", "invokefunction", "invokescript", "sendrawtransaction":
results = "TODO"
case "validateaddress":
@ -224,6 +226,38 @@ Methods:
} else {
results = "Invalid public account address"
}
case "getrawtransaction":
param0, err := reqParams.ValueWithType(0, "string")
if err != nil {
resultsErr = err
} else if txHash, err := util.Uint256DecodeString(param0.StringVal); err != nil {
err = errors.Wrapf(err, "param at index 0, (%s), could not be decode to Uint256", param0.StringVal)
resultsErr = NewInvalidParamsError(err.Error(), err)
} else if tx, height, err := s.chain.GetTransaction(txHash); err != nil {
err = errors.Wrapf(err, "Invalid transaction hash: %s", txHash)
resultsErr = NewInvalidParamsError(err.Error(), err)
} else if len(reqParams) >= 2 {
_header := s.chain.GetHeaderHash(int(height))
header, err := s.chain.GetHeader(_header)
if err != nil {
resultsErr = NewInvalidParamsError(err.Error(), err)
}
param1, _ := reqParams.ValueAt(1)
switch v := param1.RawValue.(type) {
case int, float64, bool, string:
if v == 0 || v == "0" || v == 0.0 || v == false || v == "false" {
results = hex.EncodeToString(tx.Bytes())
} else {
results = wrappers.NewTransactionOutputRaw(tx, header, s.chain)
}
default:
results = wrappers.NewTransactionOutputRaw(tx, header, s.chain)
}
} else {
results = hex.EncodeToString(tx.Bytes())
}
default:
resultsErr = NewMethodNotFoundError(fmt.Sprintf("Method '%s' not supported", req.Method), nil)

View file

@ -15,21 +15,203 @@ import (
"github.com/CityOfZion/neo-go/pkg/core"
"github.com/CityOfZion/neo-go/pkg/network"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type tc struct {
rpcCall string
method string
expectedResult string
}
var testRpcCases = []tc{
{`{"jsonrpc": "2.0", "id": 1, "method": "getassetstate", "params": ["602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"] }`,
"getassetstate_1",
`{"jsonrpc":"2.0","result":{"assetId":"0x602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7","assetType":1,"name":"NEOGas","amount":"100000000","available":"0","precision":8,"fee":0,"address":"0x0000000000000000000000000000000000000000","owner":"00","admin":"AWKECj9RD8rS8RPcpCgYVjk1DeYyHwxZm3","issuer":"AFmseVrdL9f9oyCzZefL9tG6UbvhPbdYzM","expiration":0,"is_frozen":false},"id":1}`},
{`{ "jsonrpc": "2.0", "id": 1, "method": "getassetstate", "params": ["c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b"] }`,
"getassetstate_2",
`{"jsonrpc":"2.0","result":{"assetId":"0xc56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b","assetType":0,"name":"NEO","amount":"100000000","available":"0","precision":0,"fee":0,"address":"0x0000000000000000000000000000000000000000","owner":"00","admin":"Abf2qMs1pzQb8kYk9RuxtUb9jtRKJVuBJt","issuer":"AFmseVrdL9f9oyCzZefL9tG6UbvhPbdYzM","expiration":0,"is_frozen":false},"id":1}`},
{
rpcCall: `{"jsonrpc": "2.0", "id": 1, "method": "getassetstate", "params": ["62c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"] }`,
method: "getassetstate_3",
expectedResult: `{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid Params"},"id":1}`,
},
{
rpcCall: `{"jsonrpc": "2.0", "id": 1, "method": "getassetstate", "params": [123] }`,
method: "getassetstate_4",
expectedResult: `{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid Params"},"id":1}`,
},
{`{"jsonrpc": "2.0", "id": 1, "method": "getblockhash", "params": [10] }`,
"getblockhash_1",
`{"jsonrpc":"2.0","result":"0xd69e7a1f62225a35fed91ca578f33447d93fa0fd2b2f662b957e19c38c1dab1e","id":1}`},
{
rpcCall: `{"jsonrpc": "2.0", "id": 1, "method": "getblockhash", "params": [-2] }`,
method: "getblockhash_2",
expectedResult: `{"jsonrpc":"2.0","error":{"code":-32603,"message":"Internal error","data":"Internal server error"},"id":1}`,
},
{`{"jsonrpc": "2.0", "id": 1, "method": "getblock", "params": [10] }`,
"getblock",
`{"jsonrpc":"2.0","result":{"version":0,"previousblockhash":"0x7c5b4c8a70336bf68e8679be7c9a2a15f85c0f6d0e14389019dcc3edfab2bb4b","merkleroot":"0xc027979ad29226b7d34523b1439a64a6cf57fe3f4e823e9d3e90d43934783d26","time":1529926220,"height":10,"nonce":8313828522725096825,"next_consensus":"0xbe48d3a3f5d10013ab9ffee489706078714f1ea2","script":{"invocation":"40ac828e1c2a214e4d356fd2eccc7c7be9ef426f8e4ea67a50464e90ca4367e611c4c5247082b85a7d5ed985cfb90b9af2f1195531038f49c63fb6894b517071ea40b22b83d9457ca5c4c5bb2d8d7e95333820611d447bb171ce7b8af3b999d0a5a61c2301cdd645a33a47defd09c0f237a0afc86e9a84c2fe675d701e4015c0302240a6899296660c612736edc22f8d630927649d4ef1301868079032d80aae6cc1e21622f256497a84a71d7afeeef4c124135f611db24a0f7ab3d2a6886f15db7865","verification":"532102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd622102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc22103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee69954ae"},"tx":[{"type":"MinerTransaction","version":0,"attributes":null,"vin":null,"vout":null,"scripts":null}],"confirmations":12338,"nextblockhash":"0x2b1c78633dae7ab81f64362e0828153079a17b018d779d0406491f84c27b086f","hash":"0xd69e7a1f62225a35fed91ca578f33447d93fa0fd2b2f662b957e19c38c1dab1e"},"id":1}`},
{`{"jsonrpc": "2.0", "id": 1, "method": "getblockcount", "params": [] }`,
"getblockcount",
`{"jsonrpc":"2.0","result":12349,"id":1}`},
{`{"jsonrpc": "2.0", "id": 1, "method": "getconnectioncount", "params": [] }`,
"getconnectioncount",
`{"jsonrpc":"2.0","result":0,"id":1}`},
{`{"jsonrpc": "2.0", "id": 1, "method": "getbestblockhash", "params": [] }`,
"getbestblockhash",
`{"jsonrpc":"2.0","result":"877f5f2084181b85ce4726ab0a86bea6cc82cdbcb6f2eb59e6b04d27fd10929c","id":1}`},
{`{"jsonrpc": "2.0", "id": 1, "method": "getpeers", "params": [] }`,
"getpeers",
`{"jsonrpc":"2.0","result":{"unconnected":[],"connected":[],"bad":[]},"id":1}`},
// Good case, valid transaction ((param[1]=1 -> verbose = 1))
{
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["f999c36145a41306c846ea80290416143e8e856559818065be3f4e143c60e43a", 1] }`,
method: "getrawtransaction_1",
expectedResult: `{"jsonrpc":"2.0","result":{"type":"ContractTransaction","version":0,"attributes":[{"data":"23ba2703c53263e8d6e522dc32203339dcd8eee9","usage":"Script"}],"vin":[{"txid":"0x539084697cc220916cb5b16d2805945ec9f267aa004b6688fbf15e116c846aff","vout":0}],"vout":[{"address":"AXpNr3SDfLXbPHNdqxYeHK5cYpKMHZxMZ9","asset":"0xc56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b","n":0,"value":"10000"},{"address":"AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y","asset":"0xc56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b","n":1,"value":"99990000"}],"scripts":[{"invocation":"40a88bd1fcfba334b06da0ce1a679f80711895dade50352074e79e438e142dc95528d04a00c579398cb96c7301428669a09286ae790459e05e907c61ab8a1191c6","verification":"21031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4aac"}],"txid":"0xf999c36145a41306c846ea80290416143e8e856559818065be3f4e143c60e43a","size":283,"sys_fee":"0","net_fee":"0","blockhash":"0x6088bf9d3b55c67184f60b00d2e380228f713b4028b24c1719796dcd2006e417","confirmations":2902,"blocktime":1533756500},"id":1}`,
},
// Good case, valid transaction (param[1]=3 -> verbose = 1. Following the C# any number different from 0 is interpreted as verbose = 1)
{
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["f999c36145a41306c846ea80290416143e8e856559818065be3f4e143c60e43a", 3] }`,
method: "getrawtransaction_2",
expectedResult: `{"jsonrpc":"2.0","result":{"type":"ContractTransaction","version":0,"attributes":[{"data":"23ba2703c53263e8d6e522dc32203339dcd8eee9","usage":"Script"}],"vin":[{"txid":"0x539084697cc220916cb5b16d2805945ec9f267aa004b6688fbf15e116c846aff","vout":0}],"vout":[{"address":"AXpNr3SDfLXbPHNdqxYeHK5cYpKMHZxMZ9","asset":"0xc56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b","n":0,"value":"10000"},{"address":"AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y","asset":"0xc56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b","n":1,"value":"99990000"}],"scripts":[{"invocation":"40a88bd1fcfba334b06da0ce1a679f80711895dade50352074e79e438e142dc95528d04a00c579398cb96c7301428669a09286ae790459e05e907c61ab8a1191c6","verification":"21031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4aac"}],"txid":"0xf999c36145a41306c846ea80290416143e8e856559818065be3f4e143c60e43a","size":283,"sys_fee":"0","net_fee":"0","blockhash":"0x6088bf9d3b55c67184f60b00d2e380228f713b4028b24c1719796dcd2006e417","confirmations":2902,"blocktime":1533756500},"id":1}`,
},
// Good case, valid transaction (param[1]="dads" -> verbose = 1. Following the C# any string different from "0", "false" is interpreted as verbose = 1)
{
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["f999c36145a41306c846ea80290416143e8e856559818065be3f4e143c60e43a", "dads"] }`,
method: "getrawtransaction_3",
expectedResult: `{"jsonrpc":"2.0","result":{"type":"ContractTransaction","version":0,"attributes":[{"data":"23ba2703c53263e8d6e522dc32203339dcd8eee9","usage":"Script"}],"vin":[{"txid":"0x539084697cc220916cb5b16d2805945ec9f267aa004b6688fbf15e116c846aff","vout":0}],"vout":[{"address":"AXpNr3SDfLXbPHNdqxYeHK5cYpKMHZxMZ9","asset":"0xc56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b","n":0,"value":"10000"},{"address":"AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y","asset":"0xc56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b","n":1,"value":"99990000"}],"scripts":[{"invocation":"40a88bd1fcfba334b06da0ce1a679f80711895dade50352074e79e438e142dc95528d04a00c579398cb96c7301428669a09286ae790459e05e907c61ab8a1191c6","verification":"21031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4aac"}],"txid":"0xf999c36145a41306c846ea80290416143e8e856559818065be3f4e143c60e43a","size":283,"sys_fee":"0","net_fee":"0","blockhash":"0x6088bf9d3b55c67184f60b00d2e380228f713b4028b24c1719796dcd2006e417","confirmations":2902,"blocktime":1533756500},"id":1}`,
},
// Bad case, invalid transaction
{
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["45a41306c846ea80290416143e8e856559818065be3f4e143c60e43a", 1] }`,
method: "getrawtransaction_4",
expectedResult: `{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid Params","data":"param at index 0, (45a41306c846ea80290416143e8e856559818065be3f4e143c60e43a), could not be decode to Uint256: expected string size of 64 got 56"},"id":1}`,
},
// Good case, valid transaction
{
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["f999c36145a41306c846ea80290416143e8e856559818065be3f4e143c60e43a"] }`,
method: "getrawtransaction_5",
expectedResult: `{"jsonrpc":"2.0","result":"8000012023ba2703c53263e8d6e522dc32203339dcd8eee901ff6a846c115ef1fb88664b00aa67f2c95e9405286db1b56c9120c27c698490530000029b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50010a5d4e8000000affb37f5fdb9c6fec48d9f0eee85af82950f9b4a9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500f01b9b0986230023ba2703c53263e8d6e522dc32203339dcd8eee9014140a88bd1fcfba334b06da0ce1a679f80711895dade50352074e79e438e142dc95528d04a00c579398cb96c7301428669a09286ae790459e05e907c61ab8a1191c62321031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4aac","id":1}`,
},
// Good case, valid transaction (param[1]= 0 -> verbose = 0)
{
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["f999c36145a41306c846ea80290416143e8e856559818065be3f4e143c60e43a", 0] }`,
method: "getrawtransaction_6",
expectedResult: `{"jsonrpc":"2.0","result":"8000012023ba2703c53263e8d6e522dc32203339dcd8eee901ff6a846c115ef1fb88664b00aa67f2c95e9405286db1b56c9120c27c698490530000029b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50010a5d4e8000000affb37f5fdb9c6fec48d9f0eee85af82950f9b4a9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500f01b9b0986230023ba2703c53263e8d6e522dc32203339dcd8eee9014140a88bd1fcfba334b06da0ce1a679f80711895dade50352074e79e438e142dc95528d04a00c579398cb96c7301428669a09286ae790459e05e907c61ab8a1191c62321031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4aac","id":1}`,
},
// Good case, valid transaction (param[1]="false" -> verbose = 0)
{
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["f999c36145a41306c846ea80290416143e8e856559818065be3f4e143c60e43a", "false"] }`,
method: "getrawtransaction_6_a",
expectedResult: `{"jsonrpc":"2.0","result":"8000012023ba2703c53263e8d6e522dc32203339dcd8eee901ff6a846c115ef1fb88664b00aa67f2c95e9405286db1b56c9120c27c698490530000029b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50010a5d4e8000000affb37f5fdb9c6fec48d9f0eee85af82950f9b4a9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500f01b9b0986230023ba2703c53263e8d6e522dc32203339dcd8eee9014140a88bd1fcfba334b06da0ce1a679f80711895dade50352074e79e438e142dc95528d04a00c579398cb96c7301428669a09286ae790459e05e907c61ab8a1191c62321031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4aac","id":1}`,
},
// Good case, valid transaction (param[1]=false -> verbose = 0)
{
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["f999c36145a41306c846ea80290416143e8e856559818065be3f4e143c60e43a", false] }`,
method: "getrawtransaction_6_b",
expectedResult: `{"jsonrpc":"2.0","result":"8000012023ba2703c53263e8d6e522dc32203339dcd8eee901ff6a846c115ef1fb88664b00aa67f2c95e9405286db1b56c9120c27c698490530000029b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50010a5d4e8000000affb37f5fdb9c6fec48d9f0eee85af82950f9b4a9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500f01b9b0986230023ba2703c53263e8d6e522dc32203339dcd8eee9014140a88bd1fcfba334b06da0ce1a679f80711895dade50352074e79e438e142dc95528d04a00c579398cb96c7301428669a09286ae790459e05e907c61ab8a1191c62321031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4aac","id":1}`,
},
// Good case, valid transaction (param[1]="0" -> verbose = 0)
{
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["f999c36145a41306c846ea80290416143e8e856559818065be3f4e143c60e43a", "0"] }`,
method: "getrawtransaction_6_c",
expectedResult: `{"jsonrpc":"2.0","result":"8000012023ba2703c53263e8d6e522dc32203339dcd8eee901ff6a846c115ef1fb88664b00aa67f2c95e9405286db1b56c9120c27c698490530000029b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50010a5d4e8000000affb37f5fdb9c6fec48d9f0eee85af82950f9b4a9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500f01b9b0986230023ba2703c53263e8d6e522dc32203339dcd8eee9014140a88bd1fcfba334b06da0ce1a679f80711895dade50352074e79e438e142dc95528d04a00c579398cb96c7301428669a09286ae790459e05e907c61ab8a1191c62321031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4aac","id":1}`,
},
// Bad case, param at index 0 not a string
{
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": [123, 0] }`,
method: "getrawtransaction_7",
expectedResult: `{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid Params"},"id":1}`,
},
// Good case, valid address
{
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "getaccountstate", "params": ["AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"] }`,
method: "getaccountstate_1",
expectedResult: `{"jsonrpc":"2.0","result":{"version":0,"script_hash":"0xe9eed8dc39332032dc22e5d6e86332c50327ba23","frozen":false,"votes":[],"balances":[{"asset":"0x602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7","value":"72099.99960000"},{"asset":"0xc56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b","value":"99989900"}]},"id":1}`,
},
// Bad case, invalid address
{
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "getaccountstate", "params": ["AK2nJJpJr6o664CWJKi1QRXjqeic2zR"] }`,
method: "getaccountstate_2",
expectedResult: `{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid Params"},"id":1}`,
},
// Bad case, not string
{
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "getaccountstate", "params": [123] }`,
method: "getaccountstate_3",
expectedResult: `{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid Params"},"id":1}`,
},
// Bad case, empty params
{
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "getaccountstate", "params": [] }`,
method: "getaccountstate_4",
expectedResult: `{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid Params"},"id":1}`,
},
// Good case, valid address
{
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "validateaddress", "params": ["AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i"] }`,
method: "validateaddress_1",
expectedResult: `{"jsonrpc":"2.0","result":{"address":"AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i","isvalid":true},"id":1}`,
},
// Bad case, invalid address
{
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "validateaddress", "params": ["152f1muMCNa7goXYhYAQC61hxEgGacmncB"] }`,
method: "validateaddress_2",
expectedResult: `{"jsonrpc":"2.0","result":{"address":"152f1muMCNa7goXYhYAQC61hxEgGacmncB","isvalid":false},"id":1}`,
},
// Bad case, not string
{
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "validateaddress", "params": [1] }`,
method: "validateaddress_3",
expectedResult: `{"jsonrpc":"2.0","result":{"address":1,"isvalid":false},"id":1}`,
},
// Bad case, empty params
{
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "validateaddress", "params": [] }`,
method: "validateaddress_4",
expectedResult: `{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid Params"},"id":1}`,
},
}
func TestHandler(t *testing.T) {
// setup rpcServer server
net := config.ModeUnitTestNet
configPath := "../../config"
cfg, err := config.Load(configPath, net)
if err != nil {
t.Fatal("could not create levelDB chain", err)
}
require.NoError(t, err, "could not load config")
chain, err := core.NewBlockchainLevelDB(context.Background(), cfg)
if err != nil {
t.Fatal("could not create levelDB chain", err)
}
require.NoError(t, err, "could not create levelDB chain")
serverConfig := network.NewServerConfig(cfg)
server := network.NewServer(serverConfig, chain)
@ -38,122 +220,14 @@ func TestHandler(t *testing.T) {
// setup handler
handler := http.HandlerFunc(rpcServer.requestHandler)
var testCases = []struct {
rpcCall string
method string
expectedResult string
}{
{`{"jsonrpc": "2.0", "id": 1, "method": "getassetstate", "params": ["602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"] }`,
"getassetstate_1",
`{"jsonrpc":"2.0","result":{"assetId":"0x602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7","assetType":1,"name":"NEOGas","amount":"100000000","available":"0","precision":8,"fee":0,"address":"0x0000000000000000000000000000000000000000","owner":"00","admin":"AWKECj9RD8rS8RPcpCgYVjk1DeYyHwxZm3","issuer":"AFmseVrdL9f9oyCzZefL9tG6UbvhPbdYzM","expiration":0,"is_frozen":false},"id":1}`},
testRpcCases = append(testRpcCases, tc{
rpcCall: `{"jsonrpc": "2.0", "id": 1, "method": "getversion", "params": [] }`,
method: "getversion",
expectedResult: fmt.Sprintf(`{"jsonrpc":"2.0","result":{"port":20333,"nonce":%s,"useragent":"/NEO-GO:/"},"id":1}`, strconv.FormatUint(uint64(server.ID()), 10)),
},
)
{`{ "jsonrpc": "2.0", "id": 1, "method": "getassetstate", "params": ["c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b"] }`,
"getassetstate_2",
`{"jsonrpc":"2.0","result":{"assetId":"0xc56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b","assetType":0,"name":"NEO","amount":"100000000","available":"0","precision":0,"fee":0,"address":"0x0000000000000000000000000000000000000000","owner":"00","admin":"Abf2qMs1pzQb8kYk9RuxtUb9jtRKJVuBJt","issuer":"AFmseVrdL9f9oyCzZefL9tG6UbvhPbdYzM","expiration":0,"is_frozen":false},"id":1}`},
{
rpcCall: `{"jsonrpc": "2.0", "id": 1, "method": "getassetstate", "params": ["62c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"] }`,
method: "getassetstate_3",
expectedResult: `{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid Params"},"id":1}`,
},
{
rpcCall: `{"jsonrpc": "2.0", "id": 1, "method": "getassetstate", "params": [123] }`,
method: "getassetstate_4",
expectedResult: `{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid Params"},"id":1}`,
},
{`{"jsonrpc": "2.0", "id": 1, "method": "getblockhash", "params": [10] }`,
"getblockhash_1",
`{"jsonrpc":"2.0","result":"0xd69e7a1f62225a35fed91ca578f33447d93fa0fd2b2f662b957e19c38c1dab1e","id":1}`},
{
rpcCall: `{"jsonrpc": "2.0", "id": 1, "method": "getblockhash", "params": [-2] }`,
method: "getblockhash_2",
expectedResult: `{"jsonrpc":"2.0","error":{"code":-32603,"message":"Internal error","data":"Internal server error"},"id":1}`,
},
{`{"jsonrpc": "2.0", "id": 1, "method": "getblock", "params": [10] }`,
"getblock",
`{"jsonrpc":"2.0","result":{"version":0,"previousblockhash":"0x7c5b4c8a70336bf68e8679be7c9a2a15f85c0f6d0e14389019dcc3edfab2bb4b","merkleroot":"0xc027979ad29226b7d34523b1439a64a6cf57fe3f4e823e9d3e90d43934783d26","time":1529926220,"height":10,"nonce":8313828522725096825,"next_consensus":"0xbe48d3a3f5d10013ab9ffee489706078714f1ea2","script":{"invocation":"40ac828e1c2a214e4d356fd2eccc7c7be9ef426f8e4ea67a50464e90ca4367e611c4c5247082b85a7d5ed985cfb90b9af2f1195531038f49c63fb6894b517071ea40b22b83d9457ca5c4c5bb2d8d7e95333820611d447bb171ce7b8af3b999d0a5a61c2301cdd645a33a47defd09c0f237a0afc86e9a84c2fe675d701e4015c0302240a6899296660c612736edc22f8d630927649d4ef1301868079032d80aae6cc1e21622f256497a84a71d7afeeef4c124135f611db24a0f7ab3d2a6886f15db7865","verification":"532102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd622102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc22103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee69954ae"},"tx":[{"type":0,"version":0,"attributes":null,"vin":null,"vout":null,"scripts":null}],"confirmations":12338,"nextblockhash":"0x2b1c78633dae7ab81f64362e0828153079a17b018d779d0406491f84c27b086f","hash":"0xd69e7a1f62225a35fed91ca578f33447d93fa0fd2b2f662b957e19c38c1dab1e"},"id":1}`},
{`{"jsonrpc": "2.0", "id": 1, "method": "getblockcount", "params": [] }`,
"getblockcount",
`{"jsonrpc":"2.0","result":12349,"id":1}`},
{`{"jsonrpc": "2.0", "id": 1, "method": "getconnectioncount", "params": [] }`,
"getconnectioncount",
`{"jsonrpc":"2.0","result":0,"id":1}`},
{`{"jsonrpc": "2.0", "id": 1, "method": "getversion", "params": [] }`,
"getversion",
fmt.Sprintf(`{"jsonrpc":"2.0","result":{"port":20333,"nonce":%s,"useragent":"/NEO-GO:/"},"id":1}`, strconv.FormatUint(uint64(server.ID()), 10))},
{`{"jsonrpc": "2.0", "id": 1, "method": "getbestblockhash", "params": [] }`,
"getbestblockhash",
`{"jsonrpc":"2.0","result":"877f5f2084181b85ce4726ab0a86bea6cc82cdbcb6f2eb59e6b04d27fd10929c","id":1}`},
{`{"jsonrpc": "2.0", "id": 1, "method": "getpeers", "params": [] }`,
"getpeers",
`{"jsonrpc":"2.0","result":{"unconnected":[],"connected":[],"bad":[]},"id":1}`},
// Good case, valid address
{
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "getaccountstate", "params": ["AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"] }`,
method: "getaccountstate_1",
expectedResult: `{"jsonrpc":"2.0","result":{"version":0,"script_hash":"0xe9eed8dc39332032dc22e5d6e86332c50327ba23","frozen":false,"votes":[],"balances":[{"asset":"0x602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7","value":"72099.99960000"},{"asset":"0xc56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b","value":"99989900"}]},"id":1}`,
},
// Bad case, invalid address
{
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "getaccountstate", "params": ["AK2nJJpJr6o664CWJKi1QRXjqeic2zR"] }`,
method: "getaccountstate_2",
expectedResult: `{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid Params"},"id":1}`,
},
// Bad case, not string
{
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "getaccountstate", "params": [123] }`,
method: "getaccountstate_3",
expectedResult: `{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid Params"},"id":1}`,
},
// Bad case, empty params
{
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "getaccountstate", "params": [] }`,
method: "getaccountstate_4",
expectedResult: `{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid Params"},"id":1}`,
},
// Good case, valid address
{
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "validateaddress", "params": ["AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i"] }`,
method: "validateaddress_1",
expectedResult: `{"jsonrpc":"2.0","result":{"address":"AQVh2pG732YvtNaxEGkQUei3YA4cvo7d2i","isvalid":true},"id":1}`,
},
// Bad case, invalid address
{
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "validateaddress", "params": ["152f1muMCNa7goXYhYAQC61hxEgGacmncB"] }`,
method: "validateaddress_2",
expectedResult: `{"jsonrpc":"2.0","result":{"address":"152f1muMCNa7goXYhYAQC61hxEgGacmncB","isvalid":false},"id":1}`,
},
// Bad case, not string
{
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "validateaddress", "params": [1] }`,
method: "validateaddress_3",
expectedResult: `{"jsonrpc":"2.0","result":{"address":1,"isvalid":false},"id":1}`,
},
// Bad case, empty params
{
rpcCall: `{ "jsonrpc": "2.0", "id": 1, "method": "validateaddress", "params": [] }`,
method: "validateaddress_4",
expectedResult: `{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid Params"},"id":1}`,
},
}
for _, tc := range testCases {
for _, tc := range testRpcCases {
t.Run(fmt.Sprintf("method: %s, rpc call: %s", tc.method, tc.rpcCall), func(t *testing.T) {
req := httptest.NewRequest("POST", "http://0.0.0.0:20333/", strings.NewReader(tc.rpcCall))
@ -163,10 +237,7 @@ func TestHandler(t *testing.T) {
handler(w, req)
resp := w.Result()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Errorf("could not read response from the request: %s", tc.rpcCall)
}
assert.NoErrorf(t, err, "could not read response from the request: %s", tc.rpcCall)
assert.Equal(t, tc.expectedResult, string(bytes.TrimSpace(body)))
})

View file

@ -0,0 +1,40 @@
package wrappers
import (
"github.com/CityOfZion/neo-go/pkg/core"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/util"
)
// TransactionOutputRaw is used as a wrapper to represents
// a Transaction.
type TransactionOutputRaw struct {
*transaction.Transaction
TxHash util.Uint256 `json:"txid"`
Size int `json:"size"`
SysFee util.Fixed8 `json:"sys_fee"`
NetFee util.Fixed8 `json:"net_fee"`
Blockhash util.Uint256 `json:"blockhash"`
Confirmations int `json:"confirmations"`
Timestamp uint32 `json:"blocktime"`
}
// NewTransactionOutputRaw returns a new ransactionOutputRaw object.
func NewTransactionOutputRaw(tx *transaction.Transaction, header *core.Header, chain core.Blockchainer) TransactionOutputRaw {
// confirmations formula
confirmations := int(chain.BlockHeight() - header.BlockBase.Index + 1)
// set index position
for i, o := range tx.Outputs {
o.Position = i
}
return TransactionOutputRaw{
Transaction: tx,
TxHash: tx.Hash(),
Size: tx.Size(),
SysFee: chain.SystemFee(tx),
NetFee: chain.NetworkFee(tx),
Blockhash: header.Hash(),
Confirmations: confirmations,
Timestamp: header.Timestamp,
}
}

View file

@ -41,14 +41,19 @@ func (f Fixed8) String() string {
// Value returns the original value representing the Fixed8.
func (f Fixed8) Value() int64 {
return int64(f) / int64(decimals)
return int64(f) / decimals
}
// NewFixed8 return a new Fixed8 type multiplied by decimals.
func NewFixed8(val int) Fixed8 {
// NewFixed8 returns a new Fixed8 type multiplied by decimals.
func NewFixed8(val int64) Fixed8 {
return Fixed8(decimals * val)
}
// NewFixed8FromFloat returns a new Fixed8 type multiplied by decimals.
func NewFixed8FromFloat(val float64) Fixed8 {
return Fixed8(int64(decimals * val))
}
// Fixed8DecodeString parses s which must be a fixed point number
// with precision up to 10^-8
func Fixed8DecodeString(s string) (Fixed8, error) {
@ -94,7 +99,32 @@ func (f *Fixed8) UnmarshalJSON(data []byte) error {
return nil
}
// Size returns the size in number of bytes of Fixed8.
func (f *Fixed8) Size() int {
return 8
}
// MarshalJSON implements the json marshaller interface.
func (f Fixed8) MarshalJSON() ([]byte, error) {
return []byte(`"` + f.String() + `"`), nil
}
// Satoshi defines the value of a 'Satoshi'.
func Satoshi() Fixed8 {
return NewFixed8(1)
}
// Div implements Fixd8 division operator.
func (f Fixed8) Div(i int64) Fixed8 {
return NewFixed8(f.Value() / i)
}
// Add implements Fixd8 addition operator.
func (f Fixed8) Add(g Fixed8) Fixed8 {
return NewFixed8(f.Value() + g.Value())
}
// Sub implements Fixd8 subtraction operator.
func (f Fixed8) Sub(g Fixed8) Fixed8 {
return NewFixed8(f.Value() - g.Value())
}

View file

@ -9,11 +9,11 @@ import (
)
func TestNewFixed8(t *testing.T) {
values := []int{9000, 100000000, 5, 10945}
values := []int64{9000, 100000000, 5, 10945}
for _, val := range values {
assert.Equal(t, Fixed8(val*decimals), NewFixed8(val))
assert.Equal(t, int64(val), NewFixed8(val).Value())
assert.Equal(t, val, NewFixed8(val).Value())
}
}

88
pkg/util/size.go Normal file
View file

@ -0,0 +1,88 @@
package util
import (
"fmt"
"reflect"
"github.com/CityOfZion/neo-go/pkg/io"
)
var (
bit8 byte
ui8 uint8
ui16 uint16
ui32 uint32
ui64 uint64
i8 int8
i16 int16
i32 int32
i64 int64
)
// GetVarIntSize returns the size in number of bytes of a variable integer
// (reference: GetVarSize(int value), https://github.com/neo-project/neo/blob/master/neo/IO/Helper.cs)
func GetVarIntSize(value int) int {
var size uintptr
if value < 0xFD {
size = 1 // unit8
} else if value <= 0xFFFF {
size = 3 // byte + uint16
} else {
size = 5 // byte + uint32
}
return int(size)
}
// GetVarStringSize returns the size of a variable string
// (reference: GetVarSize(this string value), https://github.com/neo-project/neo/blob/master/neo/IO/Helper.cs)
func GetVarStringSize(value string) int {
valueSize := len([]byte(value))
return GetVarIntSize(valueSize) + valueSize
}
// GetVarSize return the size om bytes of a variable. This implementation is not exactly like the C#
// (reference: GetVarSize<T>(this T[] value), https://github.com/neo-project/neo/blob/master/neo/IO/Helper.cs#L53) as in the C# variable
// like Uint160, Uint256 are not supported. @TODO: make sure to have full unit tests coverage.
func GetVarSize(value interface{}) int {
v := reflect.ValueOf(value)
switch v.Kind() {
case reflect.String:
return GetVarStringSize(v.String())
case reflect.Int,
reflect.Int8,
reflect.Int16,
reflect.Int32,
reflect.Int64:
return GetVarIntSize(int(v.Int()))
case reflect.Uint,
reflect.Uint8,
reflect.Uint16,
reflect.Uint32,
reflect.Uint64:
return GetVarIntSize(int(v.Uint()))
case reflect.Slice, reflect.Array:
valueLength := v.Len()
valueSize := 0
switch reflect.ValueOf(value).Index(0).Interface().(type) {
case io.Serializable:
for i := 0; i < valueLength; i++ {
elem := v.Index(i).Interface().(io.Serializable)
valueSize += elem.Size()
}
case uint8, int8:
valueSize = valueLength
case uint16, int16:
valueSize = valueLength * 2
case uint32, int32:
valueSize = valueLength * 4
case uint64, int64:
valueSize = valueLength * 8
}
return GetVarIntSize(valueLength) + valueSize
default:
panic(fmt.Sprintf("unable to calculate GetVarSize, %s", reflect.TypeOf(value)))
}
}

139
pkg/util/size_test.go Normal file
View file

@ -0,0 +1,139 @@
package util
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestVarSize(t *testing.T) {
testCases := []struct {
variable interface{}
name string
expected int
}{
{
252,
"test_int_1",
1,
},
{
253,
"test_int_2",
3,
},
{
65535,
"test_int_3",
3,
},
{
65536,
"test_int_4",
5,
},
{
4294967295,
"test_int_5",
5,
},
{
[]byte{1, 2, 4, 5, 6},
"test_[]byte_1",
6,
},
{
// The neo C# implementation doe not allowed this!
Uint160{1, 2, 4, 5, 6},
"test_Uint160_1",
21,
},
{[20]uint8{1, 2, 3, 4, 5, 6},
"test_uint8_1",
21,
},
{[20]uint8{1, 2, 3, 4, 5, 6, 8, 9},
"test_uint8_2",
21,
},
{[32]uint8{1, 2, 3, 4, 5, 6},
"test_uint8_3",
33,
},
{[10]uint16{1, 2, 3, 4, 5, 6},
"test_uint16_1",
21,
},
{[10]uint16{1, 2, 3, 4, 5, 6, 10, 21},
"test_uint16_2",
21,
},
{[30]uint32{1, 2, 3, 4, 5, 6, 10, 21},
"test_uint32_2",
121,
},
{[30]uint64{1, 2, 3, 4, 5, 6, 10, 21},
"test_uint64_2",
241,
},
{[20]int8{1, 2, 3, 4, 5, 6},
"test_int8_1",
21,
},
{[20]int8{-1, 2, 3, 4, 5, 6, 8, 9},
"test_int8_2",
21,
},
{[32]int8{-1, 2, 3, 4, 5, 6},
"test_int8_3",
33,
},
{[10]int16{-1, 2, 3, 4, 5, 6},
"test_int16_1",
21,
},
{[10]int16{-1, 2, 3, 4, 5, 6, 10, 21},
"test_int16_2",
21,
},
{[30]int32{-1, 2, 3, 4, 5, 6, 10, 21},
"test_int32_2",
121,
},
{[30]int64{-1, 2, 3, 4, 5, 6, 10, 21},
"test_int64_2",
241,
},
// The neo C# implementation doe not allowed this!
{Uint256{1, 2, 3, 4, 5, 6},
"test_Uint256_1",
33,
},
{"abc",
"test_string_1",
4,
},
{"abcà",
"test_string_2",
6,
},
{"2d3b96ae1bcc5a585e075e3b81920210dec16302",
"test_string_3",
41,
},
}
for _, tc := range testCases {
t.Run(fmt.Sprintf("run: %s", tc.name), func(t *testing.T) {
result := GetVarSize(tc.variable)
assert.Equal(t, tc.expected, result)
})
}
}

View file

@ -79,6 +79,11 @@ func (u *Uint160) UnmarshalJSON(data []byte) (err error) {
return err
}
// Size returns the lenght of the bytes representation of Uint160
func (u Uint160) Size() int {
return uint160Size
}
// MarshalJSON implements the json marshaller interface.
func (u Uint160) MarshalJSON() ([]byte, error) {
return []byte(`"0x` + u.String() + `"`), nil

View file

@ -65,6 +65,11 @@ func (u *Uint256) UnmarshalJSON(data []byte) (err error) {
return err
}
// Size returns the lenght of the bytes representation of Uint256
func (u Uint256) Size() int {
return uint256Size
}
// MarshalJSON implements the json marshaller interface.
func (u Uint256) MarshalJSON() ([]byte, error) {
return []byte(`"0x` + u.String() + `"`), nil