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` |
| `getconnectioncount` |
| `getcontractstate` |
| `getminimumnetworkfee` |
| `getnep5balances` |
| `getnep5transfers` |
| `getpeers` |
| `getproof` |
| `getrawmempool` |
| `getrawtransaction` |
| `getstateheight` |
| `getstateroot` |
| `getstorage` |
| `gettransactionheight` |
| `gettxout` |
@ -65,9 +69,78 @@ which would yield the response:
| `sendrawtransaction` |
| `submitblock` |
| `validateaddress` |
| `verifyproof` |
#### 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`
neo-go's implementation of `invokefunction` and `invoke` does not return `tx`

View file

@ -3,6 +3,7 @@ package core
import (
"fmt"
"math"
"math/big"
"sort"
"sync"
"sync/atomic"
@ -31,7 +32,7 @@ import (
// Tuning parameters.
const (
headerBatchCount = 2000
version = "0.0.9"
version = "0.0.10"
// 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
@ -551,9 +552,9 @@ func (bc *Blockchain) processHeader(h *block.Header, batch storage.Batch, header
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.
func (bc *Blockchain) getSystemFeeAmount(h util.Uint256) uint32 {
func (bc *Blockchain) GetSystemFeeAmount(h util.Uint256) uint32 {
_, sf, _ := bc.dao.GetBlock(h)
return sf
}
@ -582,7 +583,7 @@ func (bc *Blockchain) GetStateRoot(height uint32) (*state.MPTRootState, error) {
func (bc *Blockchain) storeBlock(block *block.Block) error {
cache := dao.NewCached(bc.dao)
appExecResults := make([]*state.AppExecResult, 0, len(block.Transactions))
fee := bc.getSystemFeeAmount(block.PrevHash)
fee := bc.GetSystemFeeAmount(block.PrevHash)
for _, tx := range block.Transactions {
fee += uint32(bc.SystemFee(tx).IntegralValue())
}
@ -688,6 +689,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
Precision: t.Precision,
Owner: t.Owner,
Admin: t.Admin,
Issuer: t.Admin,
Expiration: bc.BlockHeight() + registeredAssetLifetime,
})
if err != nil {
@ -943,16 +945,23 @@ func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, transfer *state.NEP
return
}
bs := balances.Trackers[transfer.Asset]
bs.Balance -= transfer.Amount
if bs.Balance == nil {
return
}
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)
if err != nil {
return
}
transfer.Amount = -transfer.Amount
transfer.Amount.Neg(transfer.Amount)
if isBig {
balances.NextTransferBatch++
}
@ -966,7 +975,10 @@ func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, transfer *state.NEP
return
}
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
balances.Trackers[transfer.Asset] = bs
@ -1502,9 +1514,9 @@ func (bc *Blockchain) CalculateClaimable(value util.Fixed8, startHeight, endHeig
startHeight++
}
h := bc.GetHeaderHash(int(startHeight - 1))
feeStart := bc.getSystemFeeAmount(h)
feeStart := bc.GetSystemFeeAmount(h)
h = bc.GetHeaderHash(int(endHeight - 1))
feeEnd := bc.getSystemFeeAmount(h)
feeEnd := bc.GetSystemFeeAmount(h)
sysFeeTotal := util.Fixed8(feeEnd - feeStart)
ratio := value / 100000000
@ -1576,11 +1588,44 @@ func (bc *Blockchain) NetworkFee(t *transaction.Transaction) util.Fixed8 {
// SystemFee returns system fee.
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)
if inv.Version >= 1 {
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)
}
@ -2232,23 +2277,14 @@ func (bc *Blockchain) GetEnrollments() ([]*state.Validator, error) {
for _, validator := range validators {
if validator.Registered {
result = append(result, validator)
continue
}
}
for _, sBValidator := range uniqueSBValidators {
isAdded := false
for _, v := range result {
if v.PublicKey == sBValidator {
isAdded = true
for _, sbValidator := range uniqueSBValidators {
if validator.PublicKey.Equal(sbValidator) {
result = append(result, validator)
break
}
}
if !isAdded {
result = append(result, &state.Validator{
PublicKey: sBValidator,
Registered: false,
Votes: 0,
})
}
}
return result, nil
}

View file

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

View file

@ -13,7 +13,7 @@ import (
// NEP5Tracker contains info about a single account in a NEP5 contract.
type NEP5Tracker struct {
// 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
// account occured.
LastUpdatedBlock uint32
@ -25,7 +25,7 @@ type TransferLog struct {
}
// 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.
type NEP5Transfer struct {
@ -37,7 +37,7 @@ type NEP5Transfer struct {
To util.Uint160
// Amount is the amount of tokens transferred.
// 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 uint32
// Timestamp is the timestamp of the block where transfer occured.
@ -48,6 +48,8 @@ type NEP5Transfer struct {
Index uint32
}
const amountSize = 32
// NEP5Balances is a map of the NEP5 contract hashes
// to the corresponding structures.
type NEP5Balances struct {
@ -143,13 +145,13 @@ func (lg *TransferLog) Size() int {
// EncodeBinary implements io.Serializable interface.
func (t *NEP5Tracker) EncodeBinary(w *io.BinWriter) {
w.WriteU64LE(uint64(t.Balance))
w.WriteVarBytes(emit.IntToBytes(t.Balance))
w.WriteU32LE(t.LastUpdatedBlock)
}
// DecodeBinary implements io.Serializable interface.
func (t *NEP5Tracker) DecodeBinary(r *io.BinReader) {
t.Balance = int64(r.ReadU64LE())
t.Balance = emit.BytesToInt(r.ReadVarBytes(amountSize))
t.LastUpdatedBlock = r.ReadU32LE()
}
@ -194,7 +196,7 @@ func NEP5TransferFromNotification(ne NotificationEvent, txHash util.Uint256, hei
Asset: ne.ScriptHash,
From: fromAddr,
To: toAddr,
Amount: amount.Int64(),
Amount: amount,
Block: height,
Timestamp: time,
Tx: txHash,
@ -212,7 +214,19 @@ func (t *NEP5Transfer) EncodeBinary(w *io.BinWriter) {
w.WriteBytes(t.To[:])
w.WriteU32LE(t.Block)
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)
}
@ -224,6 +238,8 @@ func (t *NEP5Transfer) DecodeBinary(r *io.BinReader) {
r.ReadBytes(t.To[:])
t.Block = 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()
}

View file

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

View file

@ -1,5 +1,10 @@
package transaction
import (
"errors"
"fmt"
)
// AssetType represents a NEO asset type.
type AssetType uint8
@ -14,3 +19,68 @@ const (
Invoice AssetType = DutyFlag | 0x18
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
type publishedContract struct {
Code publishedCode `json:"code"`
NeedStorage bool `json:"needstorage,omitempty"`
Name string `json:"name,omitempty"`
CodeVersion string `json:"version,omitempty"`
Author string `json:"author,omitempty"`
Email string `json:"email,omitempty"`
Description string `json:"description,omitempty"`
NeedStorage bool `json:"needstorage"`
Name string `json:"name"`
CodeVersion string `json:"version"`
Author string `json:"author"`
Email string `json:"email"`
Description string `json:"description"`
}
// publishedCode is a JSON wrapper for PublishTransaction Code
type publishedCode struct {
Hash util.Uint160 `json:"hash,omitempty"`
Script string `json:"script,omitempty"`
ParamList []smartcontract.ParamType `json:"parameters,omitempty"`
ReturnType smartcontract.ParamType `json:"returntype,omitempty"`
Hash util.Uint160 `json:"hash"`
Script string `json:"script"`
ParamList []smartcontract.ParamType `json:"parameters"`
ReturnType smartcontract.ParamType `json:"returntype"`
}

View file

@ -56,10 +56,10 @@ func (tx *RegisterTX) EncodeBinary(bw *io.BinWriter) {
// registeredAsset is a wrapper for RegisterTransaction
type registeredAsset struct {
AssetType AssetType `json:"type,omitempty"`
Name json.RawMessage `json:"name,omitempty"`
Amount util.Fixed8 `json:"amount,omitempty"`
Precision uint8 `json:"precision,omitempty"`
Owner keys.PublicKey `json:"owner,omitempty"`
Admin string `json:"admin,omitempty"`
AssetType AssetType `json:"type"`
Name json.RawMessage `json:"name"`
Amount util.Fixed8 `json:"amount"`
Precision uint8 `json:"precision"`
Owner keys.PublicKey `json:"owner"`
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 {
panic("TODO")
}
func (chain testChain) GetSystemFeeAmount(h util.Uint256) uint32 {
panic("TODO")
}
func (chain testChain) GetTestVM(tx *transaction.Transaction) *vm.VM {
panic("TODO")
}

View file

@ -178,7 +178,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
invoke: func(c *Client) (interface{}, error) {
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{} {
return &result.AssetState{
ID: core.GoverningTokenID(),
@ -517,25 +517,25 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
invoke: func(c *Client) (interface{}, error) {
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{} {
return &result.GetPeers{
Unconnected: result.Peers{
{
Address: "172.200.0.1",
Port: "20333",
Port: 20333,
},
},
Connected: result.Peers{
{
Address: "127.0.0.1",
Port: "20335",
Port: 20335,
},
},
Bad: result.Peers{
{
Address: "172.200.0.254",
Port: "20332",
Port: 20332,
},
},
}

View file

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

View file

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

View file

@ -1,6 +1,7 @@
package result
import (
"strconv"
"strings"
)
@ -18,7 +19,7 @@ type (
// Peer represents the peer.
Peer struct {
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) {
for i := range addrs {
addressParts := strings.Split(addrs[i], ":")
port, _ := strconv.Atoi(addressParts[1]) // We know it's a good port number.
peer := Peer{
Address: addressParts[0],
Port: addressParts[1],
Port: uint16(port),
}
*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.Bad))
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, "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{
N: out.Position,
Asset: "0x" + out.AssetID.String(),
Asset: "0x" + out.AssetID.StringLE(),
Value: out.Amount,
Address: addr,
}

View file

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

View file

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