diff --git a/config/config.go b/config/config.go index 72d2061e2..2fcfba53a 100644 --- a/config/config.go +++ b/config/config.go @@ -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) + } +} diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 9324ce46b..3e294681e 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -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) diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index 810c77622..7dbe23637 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -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 +} diff --git a/pkg/core/blockchainer.go b/pkg/core/blockchainer.go index a0cde74e2..9eec695f0 100644 --- a/pkg/core/blockchainer.go +++ b/pkg/core/blockchainer.go @@ -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 } diff --git a/pkg/core/transaction/attr_usage.go b/pkg/core/transaction/attr_usage.go index 78bc43f21..6cff1978c 100644 --- a/pkg/core/transaction/attr_usage.go +++ b/pkg/core/transaction/attr_usage.go @@ -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" +} diff --git a/pkg/core/transaction/attribute.go b/pkg/core/transaction/attribute.go index 694f62a24..783b1dcc3 100644 --- a/pkg/core/transaction/attribute.go +++ b/pkg/core/transaction/attribute.go @@ -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), + }) +} diff --git a/pkg/core/transaction/input.go b/pkg/core/transaction/input.go index a4e2ce273..15cc617e0 100644 --- a/pkg/core/transaction/input.go +++ b/pkg/core/transaction/input.go @@ -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 +} diff --git a/pkg/core/transaction/output.go b/pkg/core/transaction/output.go index f061f3d4f..2898605aa 100644 --- a/pkg/core/transaction/output.go +++ b/pkg/core/transaction/output.go @@ -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, + }) +} diff --git a/pkg/core/transaction/transaction.go b/pkg/core/transaction/transaction.go index 5f3fe2145..3dea806cb 100644 --- a/pkg/core/transaction/transaction.go +++ b/pkg/core/transaction/transaction.go @@ -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() + +} diff --git a/pkg/core/transaction/type.go b/pkg/core/transaction/type.go index e049b3720..ae0d8982c 100644 --- a/pkg/core/transaction/type.go +++ b/pkg/core/transaction/type.go @@ -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 +} diff --git a/pkg/core/transaction/witness.go b/pkg/core/transaction/witness.go index 0f763172c..83dfefaac 100644 --- a/pkg/core/transaction/witness.go +++ b/pkg/core/transaction/witness.go @@ -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) +} diff --git a/pkg/core/util.go b/pkg/core/util.go index 654cb1a2f..086f54e57 100644 --- a/pkg/core/util.go +++ b/pkg/core/util.go @@ -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. diff --git a/pkg/io/serializable.go b/pkg/io/serializable.go new file mode 100644 index 000000000..359c8513e --- /dev/null +++ b/pkg/io/serializable.go @@ -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 +} diff --git a/pkg/network/helper_test.go b/pkg/network/helper_test.go index 6e626ffbb..f28f5fd63 100644 --- a/pkg/network/helper_test.go +++ b/pkg/network/helper_test.go @@ -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{} diff --git a/pkg/rpc/server.go b/pkg/rpc/server.go index faa19548d..07576b52a 100644 --- a/pkg/rpc/server.go +++ b/pkg/rpc/server.go @@ -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) diff --git a/pkg/rpc/server_test.go b/pkg/rpc/server_test.go index 12ec40144..9bc923deb 100644 --- a/pkg/rpc/server_test.go +++ b/pkg/rpc/server_test.go @@ -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))) }) diff --git a/pkg/rpc/wrappers/tx_raw_output.go b/pkg/rpc/wrappers/tx_raw_output.go new file mode 100644 index 000000000..d3ebbd33a --- /dev/null +++ b/pkg/rpc/wrappers/tx_raw_output.go @@ -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, + } +} diff --git a/pkg/util/fixed8.go b/pkg/util/fixed8.go index b158d18ef..6eccc5388 100644 --- a/pkg/util/fixed8.go +++ b/pkg/util/fixed8.go @@ -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()) +} diff --git a/pkg/util/fixed8_test.go b/pkg/util/fixed8_test.go index 76d49d223..a6b82e577 100644 --- a/pkg/util/fixed8_test.go +++ b/pkg/util/fixed8_test.go @@ -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()) } } diff --git a/pkg/util/size.go b/pkg/util/size.go new file mode 100644 index 000000000..58a8a04c7 --- /dev/null +++ b/pkg/util/size.go @@ -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(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))) + } +} diff --git a/pkg/util/size_test.go b/pkg/util/size_test.go new file mode 100644 index 000000000..f4c57b475 --- /dev/null +++ b/pkg/util/size_test.go @@ -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) + }) + } +} diff --git a/pkg/util/uint160.go b/pkg/util/uint160.go index 84251841f..9f1341e28 100644 --- a/pkg/util/uint160.go +++ b/pkg/util/uint160.go @@ -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 diff --git a/pkg/util/uint256.go b/pkg/util/uint256.go index 4a699c890..b7720a7a4 100644 --- a/pkg/util/uint256.go +++ b/pkg/util/uint256.go @@ -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