Merge pull request #1760 from nspcc-dev/rpc-compatibility-fixes-and-docs

[2.x] RPC compatibility (and other) fixes and docs
This commit is contained in:
Roman Khimov 2021-02-19 12:30:59 +03:00 committed by GitHub
commit 3f50f90dc5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 293 additions and 101 deletions

View file

@ -46,11 +46,15 @@ which would yield the response:
| `getclaimable` | | `getclaimable` |
| `getconnectioncount` | | `getconnectioncount` |
| `getcontractstate` | | `getcontractstate` |
| `getminimumnetworkfee` |
| `getnep5balances` | | `getnep5balances` |
| `getnep5transfers` | | `getnep5transfers` |
| `getpeers` | | `getpeers` |
| `getproof` |
| `getrawmempool` | | `getrawmempool` |
| `getrawtransaction` | | `getrawtransaction` |
| `getstateheight` |
| `getstateroot` |
| `getstorage` | | `getstorage` |
| `gettransactionheight` | | `gettransactionheight` |
| `gettxout` | | `gettxout` |
@ -65,9 +69,78 @@ which would yield the response:
| `sendrawtransaction` | | `sendrawtransaction` |
| `submitblock` | | `submitblock` |
| `validateaddress` | | `validateaddress` |
| `verifyproof` |
#### Implementation notices #### Implementation notices
##### `getaccountstate`
The order of assets in `balances` section may differ from the one returned by
C# implementation. Assets can still be identified by their hashes so it
shouldn't be an issue.
##### `getapplicationlog`
Error handling for incorrect stack items differs with C# implementation. C#
implementation substitutes `stack` and `state` arrays with "error: recursive
reference" string if there are any invalid items. NeoGo never does this, for
bad `state` items it uses byte array susbstitute with message "bad
notification: ..." (may vary depending on the problem), for incorrect `stack`
items it just omits them (still returning valid ones).
##### `getassetstate`
It returns "NEO" for NEO and "NEOGas" for GAS in the `name` field instead of
language-aware JSON structures.
##### `getblock` and `getrawtransaction`
In their verbose outputs neo-go can omit some fields with default values for
transactions, this includes:
* zero "nonce" for Miner transactions (usually nonce is not zero)
* zero "gas" for Invocation transactions (most of the time it is zero).
##### `getclaimable`
`claimable` array ordering differs, neo-go orders entries there by the
`end_height` field, while C# implementation orders by `txid`.
##### `getcontractstate`
C# implementation doesn't return `Payable` flag in its output, neo-go has
`is_payable` field in `properties` for that.
##### `getnep5transfers`
`received` and `sent` entries are sorted differently, C# node uses
chronological order and neo-go uses reverse chronological order (which is
important for paging support, see Extensions section down below).
##### `getrawmempool`
neo-go doesn't support boolean parameter to `getrawmempool` for unverified
transactions request because neo-go actually never stores unverified
transactions in the mempool.
##### `getunclaimed`
Numeric results are wrapped into strings in neo-go (the same way fees are
encoded) to prevent floating point rounding errors.
##### `getunspents`
neo-go uses standard "0xhash" syntax for `txid` and `asset_hash` fields
whereas C# module doesn't add "0x" prefix. The order of `balance` or `unspent`
entries can differ. neo-go returns all UTXO assets while C# module only tracks
and returns NEO and GAS.
##### `getutxotransfers`
`transactions` are sorted differently, C# node uses chronological order and
neo-go uses reverse chronological order (which is important for paging
support, see Extensions section down below).
##### `invokefunction` and `invoke` ##### `invokefunction` and `invoke`
neo-go's implementation of `invokefunction` and `invoke` does not return `tx` neo-go's implementation of `invokefunction` and `invoke` does not return `tx`

View file

@ -3,6 +3,7 @@ package core
import ( import (
"fmt" "fmt"
"math" "math"
"math/big"
"sort" "sort"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -31,7 +32,7 @@ import (
// Tuning parameters. // Tuning parameters.
const ( const (
headerBatchCount = 2000 headerBatchCount = 2000
version = "0.0.9" version = "0.0.10"
// This one comes from C# code and it's different from the constant used // This one comes from C# code and it's different from the constant used
// when creating an asset with Neo.Asset.Create interop call. It looks // when creating an asset with Neo.Asset.Create interop call. It looks
@ -551,9 +552,9 @@ func (bc *Blockchain) processHeader(h *block.Header, batch storage.Batch, header
return nil return nil
} }
// bc.GetHeaderHash(int(endHeight)) returns sum of all system fees for blocks up to h. // GetSystemFeeAmount returns sum of all system fees for blocks up to h.
// and 0 if no such block exists. // and 0 if no such block exists.
func (bc *Blockchain) getSystemFeeAmount(h util.Uint256) uint32 { func (bc *Blockchain) GetSystemFeeAmount(h util.Uint256) uint32 {
_, sf, _ := bc.dao.GetBlock(h) _, sf, _ := bc.dao.GetBlock(h)
return sf return sf
} }
@ -582,7 +583,7 @@ func (bc *Blockchain) GetStateRoot(height uint32) (*state.MPTRootState, error) {
func (bc *Blockchain) storeBlock(block *block.Block) error { func (bc *Blockchain) storeBlock(block *block.Block) error {
cache := dao.NewCached(bc.dao) cache := dao.NewCached(bc.dao)
appExecResults := make([]*state.AppExecResult, 0, len(block.Transactions)) appExecResults := make([]*state.AppExecResult, 0, len(block.Transactions))
fee := bc.getSystemFeeAmount(block.PrevHash) fee := bc.GetSystemFeeAmount(block.PrevHash)
for _, tx := range block.Transactions { for _, tx := range block.Transactions {
fee += uint32(bc.SystemFee(tx).IntegralValue()) fee += uint32(bc.SystemFee(tx).IntegralValue())
} }
@ -688,6 +689,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
Precision: t.Precision, Precision: t.Precision,
Owner: t.Owner, Owner: t.Owner,
Admin: t.Admin, Admin: t.Admin,
Issuer: t.Admin,
Expiration: bc.BlockHeight() + registeredAssetLifetime, Expiration: bc.BlockHeight() + registeredAssetLifetime,
}) })
if err != nil { if err != nil {
@ -943,16 +945,23 @@ func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, transfer *state.NEP
return return
} }
bs := balances.Trackers[transfer.Asset] bs := balances.Trackers[transfer.Asset]
bs.Balance -= transfer.Amount if bs.Balance == nil {
bs.LastUpdatedBlock = transfer.Block return
balances.Trackers[transfer.Asset] = bs }
bs.Balance.Sub(bs.Balance, transfer.Amount)
if bs.Balance.Sign() > 0 {
bs.LastUpdatedBlock = transfer.Block
balances.Trackers[transfer.Asset] = bs
} else {
delete(balances.Trackers, transfer.Asset)
}
transfer.Amount = -transfer.Amount transfer.Amount.Neg(transfer.Amount)
isBig, err := cache.AppendNEP5Transfer(transfer.From, balances.NextTransferBatch, transfer) isBig, err := cache.AppendNEP5Transfer(transfer.From, balances.NextTransferBatch, transfer)
if err != nil { if err != nil {
return return
} }
transfer.Amount = -transfer.Amount transfer.Amount.Neg(transfer.Amount)
if isBig { if isBig {
balances.NextTransferBatch++ balances.NextTransferBatch++
} }
@ -966,7 +975,10 @@ func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, transfer *state.NEP
return return
} }
bs := balances.Trackers[transfer.Asset] bs := balances.Trackers[transfer.Asset]
bs.Balance += transfer.Amount if bs.Balance == nil {
bs.Balance = new(big.Int)
}
bs.Balance.Add(bs.Balance, transfer.Amount)
bs.LastUpdatedBlock = transfer.Block bs.LastUpdatedBlock = transfer.Block
balances.Trackers[transfer.Asset] = bs balances.Trackers[transfer.Asset] = bs
@ -1502,9 +1514,9 @@ func (bc *Blockchain) CalculateClaimable(value util.Fixed8, startHeight, endHeig
startHeight++ startHeight++
} }
h := bc.GetHeaderHash(int(startHeight - 1)) h := bc.GetHeaderHash(int(startHeight - 1))
feeStart := bc.getSystemFeeAmount(h) feeStart := bc.GetSystemFeeAmount(h)
h = bc.GetHeaderHash(int(endHeight - 1)) h = bc.GetHeaderHash(int(endHeight - 1))
feeEnd := bc.getSystemFeeAmount(h) feeEnd := bc.GetSystemFeeAmount(h)
sysFeeTotal := util.Fixed8(feeEnd - feeStart) sysFeeTotal := util.Fixed8(feeEnd - feeStart)
ratio := value / 100000000 ratio := value / 100000000
@ -1576,11 +1588,44 @@ func (bc *Blockchain) NetworkFee(t *transaction.Transaction) util.Fixed8 {
// SystemFee returns system fee. // SystemFee returns system fee.
func (bc *Blockchain) SystemFee(t *transaction.Transaction) util.Fixed8 { func (bc *Blockchain) SystemFee(t *transaction.Transaction) util.Fixed8 {
if t.Type == transaction.InvocationType { switch t.Type {
case transaction.InvocationType:
inv := t.Data.(*transaction.InvocationTX) inv := t.Data.(*transaction.InvocationTX)
if inv.Version >= 1 { return inv.Gas
return inv.Gas case transaction.IssueType:
if t.Version >= 1 {
return util.Fixed8(0)
} }
var iszero = true
for i := range t.Outputs {
asset := t.Outputs[i].AssetID
if asset != UtilityTokenID() && asset != GoverningTokenID() {
iszero = false
break
}
}
if iszero {
return util.Fixed8(0)
}
case transaction.RegisterType:
reg := t.Data.(*transaction.RegisterTX)
if reg.AssetType == transaction.GoverningToken || reg.AssetType == transaction.UtilityToken {
return util.Fixed8(0)
}
case transaction.StateType:
res := util.Fixed8(0)
st := t.Data.(*transaction.StateTX)
for _, desc := range st.Descriptors {
if desc.Type == transaction.Validator && desc.Field == "Registered" {
for i := range desc.Value {
if desc.Value[i] != 0 {
res += util.Fixed8FromInt64(1000)
break
}
}
}
}
return res
} }
return bc.GetConfig().SystemFee.TryGetValue(t.Type) return bc.GetConfig().SystemFee.TryGetValue(t.Type)
} }
@ -2232,23 +2277,14 @@ func (bc *Blockchain) GetEnrollments() ([]*state.Validator, error) {
for _, validator := range validators { for _, validator := range validators {
if validator.Registered { if validator.Registered {
result = append(result, validator) result = append(result, validator)
continue
} }
} for _, sbValidator := range uniqueSBValidators {
for _, sBValidator := range uniqueSBValidators { if validator.PublicKey.Equal(sbValidator) {
isAdded := false result = append(result, validator)
for _, v := range result {
if v.PublicKey == sBValidator {
isAdded = true
break break
} }
} }
if !isAdded {
result = append(result, &state.Validator{
PublicKey: sBValidator,
Registered: false,
Votes: 0,
})
}
} }
return result, nil return result, nil
} }

View file

@ -44,6 +44,7 @@ type Blockchainer interface {
GetStateRoot(height uint32) (*state.MPTRootState, error) GetStateRoot(height uint32) (*state.MPTRootState, error)
GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem
GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error) GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error)
GetSystemFeeAmount(h util.Uint256) uint32
GetTestVM(tx *transaction.Transaction) *vm.VM GetTestVM(tx *transaction.Transaction) *vm.VM
GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error) GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error)
GetUnspentCoinState(util.Uint256) *state.UnspentCoin GetUnspentCoinState(util.Uint256) *state.UnspentCoin

View file

@ -13,7 +13,7 @@ import (
// NEP5Tracker contains info about a single account in a NEP5 contract. // NEP5Tracker contains info about a single account in a NEP5 contract.
type NEP5Tracker struct { type NEP5Tracker struct {
// Balance is the current balance of the account. // Balance is the current balance of the account.
Balance int64 Balance *big.Int
// LastUpdatedBlock is a number of block when last `transfer` to or from the // LastUpdatedBlock is a number of block when last `transfer` to or from the
// account occured. // account occured.
LastUpdatedBlock uint32 LastUpdatedBlock uint32
@ -25,7 +25,7 @@ type TransferLog struct {
} }
// NEP5TransferSize is a size of a marshaled NEP5Transfer struct in bytes. // NEP5TransferSize is a size of a marshaled NEP5Transfer struct in bytes.
const NEP5TransferSize = util.Uint160Size*3 + 8 + 4 + 4 + util.Uint256Size + 4 const NEP5TransferSize = util.Uint160Size*3 + amountSize + 4 + 4 + util.Uint256Size + 4
// NEP5Transfer represents a single NEP5 Transfer event. // NEP5Transfer represents a single NEP5 Transfer event.
type NEP5Transfer struct { type NEP5Transfer struct {
@ -37,7 +37,7 @@ type NEP5Transfer struct {
To util.Uint160 To util.Uint160
// Amount is the amount of tokens transferred. // Amount is the amount of tokens transferred.
// It is negative when tokens are sent and positive if they are received. // It is negative when tokens are sent and positive if they are received.
Amount int64 Amount *big.Int
// Block is a number of block when the event occured. // Block is a number of block when the event occured.
Block uint32 Block uint32
// Timestamp is the timestamp of the block where transfer occured. // Timestamp is the timestamp of the block where transfer occured.
@ -48,6 +48,8 @@ type NEP5Transfer struct {
Index uint32 Index uint32
} }
const amountSize = 32
// NEP5Balances is a map of the NEP5 contract hashes // NEP5Balances is a map of the NEP5 contract hashes
// to the corresponding structures. // to the corresponding structures.
type NEP5Balances struct { type NEP5Balances struct {
@ -143,13 +145,13 @@ func (lg *TransferLog) Size() int {
// EncodeBinary implements io.Serializable interface. // EncodeBinary implements io.Serializable interface.
func (t *NEP5Tracker) EncodeBinary(w *io.BinWriter) { func (t *NEP5Tracker) EncodeBinary(w *io.BinWriter) {
w.WriteU64LE(uint64(t.Balance)) w.WriteVarBytes(emit.IntToBytes(t.Balance))
w.WriteU32LE(t.LastUpdatedBlock) w.WriteU32LE(t.LastUpdatedBlock)
} }
// DecodeBinary implements io.Serializable interface. // DecodeBinary implements io.Serializable interface.
func (t *NEP5Tracker) DecodeBinary(r *io.BinReader) { func (t *NEP5Tracker) DecodeBinary(r *io.BinReader) {
t.Balance = int64(r.ReadU64LE()) t.Balance = emit.BytesToInt(r.ReadVarBytes(amountSize))
t.LastUpdatedBlock = r.ReadU32LE() t.LastUpdatedBlock = r.ReadU32LE()
} }
@ -194,7 +196,7 @@ func NEP5TransferFromNotification(ne NotificationEvent, txHash util.Uint256, hei
Asset: ne.ScriptHash, Asset: ne.ScriptHash,
From: fromAddr, From: fromAddr,
To: toAddr, To: toAddr,
Amount: amount.Int64(), Amount: amount,
Block: height, Block: height,
Timestamp: time, Timestamp: time,
Tx: txHash, Tx: txHash,
@ -212,7 +214,19 @@ func (t *NEP5Transfer) EncodeBinary(w *io.BinWriter) {
w.WriteBytes(t.To[:]) w.WriteBytes(t.To[:])
w.WriteU32LE(t.Block) w.WriteU32LE(t.Block)
w.WriteU32LE(t.Timestamp) w.WriteU32LE(t.Timestamp)
w.WriteU64LE(uint64(t.Amount)) am := emit.IntToBytes(t.Amount)
if len(am) > amountSize {
panic("bad integer length")
}
fillerLen := amountSize - len(am)
w.WriteBytes(am)
var filler byte
if t.Amount.Sign() < 0 {
filler = 0xff
}
for i := 0; i < fillerLen; i++ {
w.WriteB(filler)
}
w.WriteU32LE(t.Index) w.WriteU32LE(t.Index)
} }
@ -224,6 +238,8 @@ func (t *NEP5Transfer) DecodeBinary(r *io.BinReader) {
r.ReadBytes(t.To[:]) r.ReadBytes(t.To[:])
t.Block = r.ReadU32LE() t.Block = r.ReadU32LE()
t.Timestamp = r.ReadU32LE() t.Timestamp = r.ReadU32LE()
t.Amount = int64(r.ReadU64LE()) amount := make([]byte, amountSize)
r.ReadBytes(amount)
t.Amount = emit.BytesToInt(amount)
t.Index = r.ReadU32LE() t.Index = r.ReadU32LE()
} }

View file

@ -1,6 +1,7 @@
package state package state
import ( import (
"math/big"
"math/rand" "math/rand"
"testing" "testing"
"time" "time"
@ -41,7 +42,7 @@ func TestNEP5TransferLog_Append(t *testing.T) {
func TestNEP5Tracker_EncodeBinary(t *testing.T) { func TestNEP5Tracker_EncodeBinary(t *testing.T) {
expected := &NEP5Tracker{ expected := &NEP5Tracker{
Balance: int64(rand.Uint64()), Balance: big.NewInt(int64(rand.Uint64())),
LastUpdatedBlock: rand.Uint32(), LastUpdatedBlock: rand.Uint32(),
} }
@ -53,7 +54,7 @@ func TestNEP5Transfer_DecodeBinary(t *testing.T) {
Asset: util.Uint160{1, 2, 3}, Asset: util.Uint160{1, 2, 3},
From: util.Uint160{5, 6, 7}, From: util.Uint160{5, 6, 7},
To: util.Uint160{8, 9, 10}, To: util.Uint160{8, 9, 10},
Amount: 42, Amount: big.NewInt(42),
Block: 12345, Block: 12345,
Timestamp: 54321, Timestamp: 54321,
Tx: util.Uint256{8, 5, 3}, Tx: util.Uint256{8, 5, 3},
@ -70,7 +71,7 @@ func TestNEP5TransferSize(t *testing.T) {
func randomTransfer(r *rand.Rand) *NEP5Transfer { func randomTransfer(r *rand.Rand) *NEP5Transfer {
return &NEP5Transfer{ return &NEP5Transfer{
Amount: int64(r.Uint64()), Amount: big.NewInt(int64(r.Uint64())),
Block: r.Uint32(), Block: r.Uint32(),
Asset: random.Uint160(), Asset: random.Uint160(),
From: random.Uint160(), From: random.Uint160(),

View file

@ -1,5 +1,10 @@
package transaction package transaction
import (
"errors"
"fmt"
)
// AssetType represents a NEO asset type. // AssetType represents a NEO asset type.
type AssetType uint8 type AssetType uint8
@ -14,3 +19,68 @@ const (
Invoice AssetType = DutyFlag | 0x18 Invoice AssetType = DutyFlag | 0x18
Token AssetType = CreditFlag | 0x20 Token AssetType = CreditFlag | 0x20
) )
// String implements Stringer interface.
func (a AssetType) String() string {
switch a {
case CreditFlag:
return "CreditFlag"
case DutyFlag:
return "DutyFlag"
case GoverningToken:
return "GoverningToken"
case UtilityToken:
return "UtilityToken"
case Currency:
return "Currency"
case Share:
return "Share"
case Invoice:
return "Invoice"
case Token:
return "Token"
default:
return fmt.Sprintf("Unknonwn (%d)", a)
}
}
// MarshalJSON implements the json marshaller interface.
func (a AssetType) MarshalJSON() ([]byte, error) {
return []byte(`"` + a.String() + `"`), nil
}
// UnmarshalJSON implements the json.Unmarshaler interface.
func (a *AssetType) UnmarshalJSON(data []byte) error {
l := len(data)
if l < 2 || data[0] != '"' || data[l-1] != '"' {
return errors.New("wrong format")
}
var err error
*a, err = AssetTypeFromString(string(data[1 : l-1]))
return err
}
// AssetTypeFromString converts string into AssetType.
func AssetTypeFromString(s string) (AssetType, error) {
switch s {
case "CreditFlag":
return CreditFlag, nil
case "DutyFlag":
return DutyFlag, nil
case "GoverningToken":
return GoverningToken, nil
case "UtilityToken":
return UtilityToken, nil
case "Currency":
return Currency, nil
case "Share":
return Share, nil
case "Invoice":
return Invoice, nil
case "Token":
return Token, nil
default:
return 0, errors.New("bad asset type")
}
}

View file

@ -67,18 +67,18 @@ func (tx *PublishTX) EncodeBinary(bw *io.BinWriter) {
// publishedContract is a JSON wrapper for PublishTransaction // publishedContract is a JSON wrapper for PublishTransaction
type publishedContract struct { type publishedContract struct {
Code publishedCode `json:"code"` Code publishedCode `json:"code"`
NeedStorage bool `json:"needstorage,omitempty"` NeedStorage bool `json:"needstorage"`
Name string `json:"name,omitempty"` Name string `json:"name"`
CodeVersion string `json:"version,omitempty"` CodeVersion string `json:"version"`
Author string `json:"author,omitempty"` Author string `json:"author"`
Email string `json:"email,omitempty"` Email string `json:"email"`
Description string `json:"description,omitempty"` Description string `json:"description"`
} }
// publishedCode is a JSON wrapper for PublishTransaction Code // publishedCode is a JSON wrapper for PublishTransaction Code
type publishedCode struct { type publishedCode struct {
Hash util.Uint160 `json:"hash,omitempty"` Hash util.Uint160 `json:"hash"`
Script string `json:"script,omitempty"` Script string `json:"script"`
ParamList []smartcontract.ParamType `json:"parameters,omitempty"` ParamList []smartcontract.ParamType `json:"parameters"`
ReturnType smartcontract.ParamType `json:"returntype,omitempty"` ReturnType smartcontract.ParamType `json:"returntype"`
} }

View file

@ -56,10 +56,10 @@ func (tx *RegisterTX) EncodeBinary(bw *io.BinWriter) {
// registeredAsset is a wrapper for RegisterTransaction // registeredAsset is a wrapper for RegisterTransaction
type registeredAsset struct { type registeredAsset struct {
AssetType AssetType `json:"type,omitempty"` AssetType AssetType `json:"type"`
Name json.RawMessage `json:"name,omitempty"` Name json.RawMessage `json:"name"`
Amount util.Fixed8 `json:"amount,omitempty"` Amount util.Fixed8 `json:"amount"`
Precision uint8 `json:"precision,omitempty"` Precision uint8 `json:"precision"`
Owner keys.PublicKey `json:"owner,omitempty"` Owner keys.PublicKey `json:"owner"`
Admin string `json:"admin,omitempty"` Admin string `json:"admin"`
} }

View file

@ -123,6 +123,9 @@ func (chain testChain) GetStateRoot(height uint32) (*state.MPTRootState, error)
func (chain testChain) GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem { func (chain testChain) GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem {
panic("TODO") panic("TODO")
} }
func (chain testChain) GetSystemFeeAmount(h util.Uint256) uint32 {
panic("TODO")
}
func (chain testChain) GetTestVM(tx *transaction.Transaction) *vm.VM { func (chain testChain) GetTestVM(tx *transaction.Transaction) *vm.VM {
panic("TODO") panic("TODO")
} }

View file

@ -178,7 +178,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
return c.GetAssetState(util.Uint256{}) return c.GetAssetState(util.Uint256{})
}, },
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":"0xc56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b","type":0,"name":"NEO","amount":"100000000","available":"100000000","precision":0,"owner":"00","admin":"Abf2qMs1pzQb8kYk9RuxtUb9jtRKJVuBJt","issuer":"AFmseVrdL9f9oyCzZefL9tG6UbvhPbdYzM","expiration":4000000,"is_frozen":false}}`, serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":"0xc56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b","type":"GoverningToken","name":"NEO","amount":"100000000","available":"100000000","precision":0,"owner":"00","admin":"Abf2qMs1pzQb8kYk9RuxtUb9jtRKJVuBJt","issuer":"AFmseVrdL9f9oyCzZefL9tG6UbvhPbdYzM","expiration":4000000,"frozen":false}}`,
result: func(c *Client) interface{} { result: func(c *Client) interface{} {
return &result.AssetState{ return &result.AssetState{
ID: core.GoverningTokenID(), ID: core.GoverningTokenID(),
@ -517,25 +517,25 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
return c.GetPeers() return c.GetPeers()
}, },
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"unconnected":[{"address":"172.200.0.1","port":"20333"}],"connected":[{"address":"127.0.0.1","port":"20335"}],"bad":[{"address":"172.200.0.254","port":"20332"}]}}`, serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"unconnected":[{"address":"172.200.0.1","port":20333}],"connected":[{"address":"127.0.0.1","port":20335}],"bad":[{"address":"172.200.0.254","port":20332}]}}`,
result: func(c *Client) interface{} { result: func(c *Client) interface{} {
return &result.GetPeers{ return &result.GetPeers{
Unconnected: result.Peers{ Unconnected: result.Peers{
{ {
Address: "172.200.0.1", Address: "172.200.0.1",
Port: "20333", Port: 20333,
}, },
}, },
Connected: result.Peers{ Connected: result.Peers{
{ {
Address: "127.0.0.1", Address: "127.0.0.1",
Port: "20335", Port: 20335,
}, },
}, },
Bad: result.Peers{ Bad: result.Peers{
{ {
Address: "172.200.0.254", Address: "172.200.0.254",
Port: "20332", Port: 20332,
}, },
}, },
} }

View file

@ -20,7 +20,7 @@ type AssetState struct {
Admin string `json:"admin"` Admin string `json:"admin"`
Issuer string `json:"issuer"` Issuer string `json:"issuer"`
Expiration uint32 `json:"expiration"` Expiration uint32 `json:"expiration"`
IsFrozen bool `json:"is_frozen"` IsFrozen bool `json:"frozen"`
} }
// NewAssetState creates a new Asset wrapper. // NewAssetState creates a new Asset wrapper.

View file

@ -48,7 +48,7 @@ func NewBlock(b *block.Block, chain core.Blockchainer) Block {
Base: &b.Base, Base: &b.Base,
BlockMetadataAndTx: BlockMetadataAndTx{ BlockMetadataAndTx: BlockMetadataAndTx{
Size: io.GetVarSize(b), Size: io.GetVarSize(b),
Confirmations: chain.BlockHeight() - b.Index - 1, Confirmations: chain.BlockHeight() - b.Index + 1,
Tx: make([]Tx, 0, len(b.Transactions)), Tx: make([]Tx, 0, len(b.Transactions)),
}, },
} }

View file

@ -1,6 +1,7 @@
package result package result
import ( import (
"strconv"
"strings" "strings"
) )
@ -18,7 +19,7 @@ type (
// Peer represents the peer. // Peer represents the peer.
Peer struct { Peer struct {
Address string `json:"address"` Address string `json:"address"`
Port string `json:"port"` Port uint16 `json:"port"`
} }
) )
@ -50,9 +51,10 @@ func (g *GetPeers) AddBad(addrs []string) {
func (p *Peers) addPeers(addrs []string) { func (p *Peers) addPeers(addrs []string) {
for i := range addrs { for i := range addrs {
addressParts := strings.Split(addrs[i], ":") addressParts := strings.Split(addrs[i], ":")
port, _ := strconv.Atoi(addressParts[1]) // We know it's a good port number.
peer := Peer{ peer := Peer{
Address: addressParts[0], Address: addressParts[0],
Port: addressParts[1], Port: uint16(port),
} }
*p = append(*p, peer) *p = append(*p, peer)

View file

@ -20,7 +20,7 @@ func TestGetPeers(t *testing.T) {
require.Equal(t, 1, len(gp.Connected)) require.Equal(t, 1, len(gp.Connected))
require.Equal(t, 1, len(gp.Bad)) require.Equal(t, 1, len(gp.Bad))
require.Equal(t, "192.168.0.1", gp.Connected[0].Address) require.Equal(t, "192.168.0.1", gp.Connected[0].Address)
require.Equal(t, "10333", gp.Connected[0].Port) require.Equal(t, uint16(10333), gp.Connected[0].Port)
require.Equal(t, "127.0.0.1", gp.Bad[0].Address) require.Equal(t, "127.0.0.1", gp.Bad[0].Address)
require.Equal(t, "20333", gp.Bad[0].Port) require.Equal(t, uint16(20333), gp.Bad[0].Port)
} }

View file

@ -20,7 +20,7 @@ func NewTxOutput(out *transaction.Output) *TransactionOutput {
return &TransactionOutput{ return &TransactionOutput{
N: out.Position, N: out.Position,
Asset: "0x" + out.AssetID.String(), Asset: "0x" + out.AssetID.StringLE(),
Value: out.Amount, Value: out.Amount,
Address: addr, Address: addr,
} }

View file

@ -461,7 +461,7 @@ func (s *Server) getBlockHash(reqParams request.Params) (interface{}, *response.
} }
num, err := s.blockHeightFromParam(param) num, err := s.blockHeightFromParam(param)
if err != nil { if err != nil {
return nil, response.ErrInvalidParams return nil, err
} }
return s.chain.GetHeaderHash(num), nil return s.chain.GetHeaderHash(num), nil
@ -771,7 +771,7 @@ func (s *Server) getNEP5Balances(ps request.Params) (interface{}, *response.Erro
} }
if as != nil { if as != nil {
for h, bal := range as.Trackers { for h, bal := range as.Trackers {
amount := strconv.FormatInt(bal.Balance, 10) amount := bal.Balance.String()
bs.Balances = append(bs.Balances, result.NEP5Balance{ bs.Balances = append(bs.Balances, result.NEP5Balance{
Asset: h, Asset: h,
Amount: amount, Amount: amount,
@ -824,14 +824,15 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, *response.Err
NotifyIndex: tr.Index, NotifyIndex: tr.Index,
} }
if tr.Amount > 0 { // token was received if tr.Amount.Sign() > 0 { // token was received
transfer.Amount = strconv.FormatInt(tr.Amount, 10) transfer.Amount = tr.Amount.String()
if !tr.From.Equals(util.Uint160{}) { if !tr.From.Equals(util.Uint160{}) {
transfer.Address = address.Uint160ToString(tr.From) transfer.Address = address.Uint160ToString(tr.From)
} }
bs.Received = append(bs.Received, transfer) bs.Received = append(bs.Received, transfer)
} else { } else {
transfer.Amount = strconv.FormatInt(-tr.Amount, 10) tr.Amount.Neg(tr.Amount)
transfer.Amount = tr.Amount.String()
if !tr.To.Equals(util.Uint160{}) { if !tr.To.Equals(util.Uint160{}) {
transfer.Address = address.Uint160ToString(tr.To) transfer.Address = address.Uint160ToString(tr.To)
} }
@ -888,12 +889,14 @@ func uint160ToString(u util.Uint160) string {
func appendNEP5ToTransferTx(transfer *result.TransferTx, nepTr *state.NEP5Transfer) { func appendNEP5ToTransferTx(transfer *result.TransferTx, nepTr *state.NEP5Transfer) {
var event result.TransferTxEvent var event result.TransferTxEvent
event.Asset = nepTr.Asset.StringLE() event.Asset = nepTr.Asset.StringLE()
if nepTr.Amount > 0 { // token was received if nepTr.Amount.Sign() > 0 { // token was received
event.Value = strconv.FormatInt(nepTr.Amount, 10) event.Value = nepTr.Amount.String()
event.Type = "receive" event.Type = "receive"
event.Address = uint160ToString(nepTr.From) event.Address = uint160ToString(nepTr.From)
} else { } else {
event.Value = strconv.FormatInt(-nepTr.Amount, 10) nepTr.Amount.Neg(nepTr.Amount)
event.Value = nepTr.Amount.String()
nepTr.Amount.Neg(nepTr.Amount)
event.Type = "send" event.Type = "send"
event.Address = uint160ToString(nepTr.To) event.Address = uint160ToString(nepTr.To)
} }
@ -1327,7 +1330,7 @@ func (s *Server) getBlockTransferTx(ps request.Params) (interface{}, *response.E
Asset: nepTr.Asset.StringLE(), Asset: nepTr.Asset.StringLE(),
From: uint160ToString(nepTr.From), From: uint160ToString(nepTr.From),
To: uint160ToString(nepTr.To), To: uint160ToString(nepTr.To),
Value: strconv.FormatInt(nepTr.Amount, 10), Value: nepTr.Amount.String(),
}) })
index++ index++
} }
@ -1356,24 +1359,15 @@ func (s *Server) getBlockSysFee(reqParams request.Params) (interface{}, *respons
} }
headerHash := s.chain.GetHeaderHash(num) headerHash := s.chain.GetHeaderHash(num)
block, errBlock := s.chain.GetBlock(headerHash)
if errBlock != nil {
return 0, response.NewRPCError(errBlock.Error(), "", nil)
}
var blockSysFee util.Fixed8 return util.Fixed8FromInt64(int64(s.chain.GetSystemFeeAmount(headerHash))), nil
for _, tx := range block.Transactions {
blockSysFee += s.chain.SystemFee(tx)
}
return blockSysFee, nil
} }
// getBlockHeader returns the corresponding block header information according to the specified script hash. // getBlockHeader returns the corresponding block header information according to the specified script hash.
func (s *Server) getBlockHeader(reqParams request.Params) (interface{}, *response.Error) { func (s *Server) getBlockHeader(reqParams request.Params) (interface{}, *response.Error) {
hash, err := reqParams.ValueWithType(0, request.StringT).GetUint256() hash, respErr := s.getBlockHashFromParam(reqParams.Value(0))
if err != nil { if respErr != nil {
return nil, response.ErrInvalidParams return nil, respErr
} }
verbose := reqParams.Value(1).GetBoolean() verbose := reqParams.Value(1).GetBoolean()

View file

@ -548,13 +548,9 @@ var rpcTestCases = map[string][]rpcTestCase{
name: "positive", name: "positive",
params: "[1]", params: "[1]",
result: func(e *executor) interface{} { result: func(e *executor) interface{} {
block, _ := e.chain.GetBlock(e.chain.GetHeaderHash(1)) sf := e.chain.GetSystemFeeAmount(e.chain.GetHeaderHash(1))
r := util.Fixed8FromInt64(int64(sf))
var expectedBlockSysFee util.Fixed8 return &r
for _, tx := range block.Transactions {
expectedBlockSysFee += e.chain.SystemFee(tx)
}
return &expectedBlockSysFee
}, },
}, },
{ {
@ -727,12 +723,12 @@ var rpcTestCases = map[string][]rpcTestCase{
}, },
check: func(t *testing.T, e *executor, validators interface{}) { check: func(t *testing.T, e *executor, validators interface{}) {
var expected []result.Validator var expected []result.Validator
sBValidators, err := e.chain.GetStandByValidators() enrolls, err := e.chain.GetEnrollments()
require.NoError(t, err) require.NoError(t, err)
for _, sbValidator := range sBValidators { for _, validator := range enrolls {
expected = append(expected, result.Validator{ expected = append(expected, result.Validator{
PublicKey: *sbValidator, PublicKey: *validator.PublicKey,
Votes: 0, Votes: validator.Votes,
Active: true, Active: true,
}) })
} }
@ -1217,7 +1213,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
err := json.Unmarshal(res, &txOut) err := json.Unmarshal(res, &txOut)
require.NoErrorf(t, err, "could not parse response: %s", res) require.NoErrorf(t, err, "could not parse response: %s", res)
assert.Equal(t, 1, txOut.N) assert.Equal(t, 1, txOut.N)
assert.Equal(t, "0x9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5", txOut.Asset) assert.Equal(t, "0xc56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b", txOut.Asset)
assert.Equal(t, util.Fixed8FromInt64(1000), txOut.Value) assert.Equal(t, util.Fixed8FromInt64(1000), txOut.Value)
assert.Equal(t, "AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU", txOut.Address) assert.Equal(t, "AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU", txOut.Address)
}) })