rpc: fix marshalling of type-specific tx data

closes #585
This commit is contained in:
Anna Shaleva 2020-03-23 17:31:28 +03:00
parent 0be237beb6
commit 9c09ad9c89
10 changed files with 648 additions and 109 deletions

View file

@ -3,6 +3,7 @@ package transaction
import ( import (
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
) )
// PublishTX represents a publish transaction. // PublishTX represents a publish transaction.
@ -62,3 +63,22 @@ func (tx *PublishTX) EncodeBinary(bw *io.BinWriter) {
bw.WriteString(tx.Email) bw.WriteString(tx.Email)
bw.WriteString(tx.Description) bw.WriteString(tx.Description)
} }
// 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"`
}
// 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"`
}

View file

@ -1,6 +1,8 @@
package transaction package transaction
import ( import (
"encoding/json"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
@ -51,3 +53,13 @@ func (tx *RegisterTX) EncodeBinary(bw *io.BinWriter) {
bw.WriteBytes(tx.Owner.Bytes()) bw.WriteBytes(tx.Owner.Bytes())
bw.WriteBytes(tx.Admin[:]) bw.WriteBytes(tx.Admin[:])
} }
// 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"`
}

View file

@ -1,6 +1,9 @@
package transaction package transaction
import ( import (
"encoding/hex"
"encoding/json"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
) )
@ -37,3 +40,42 @@ func (s *StateDescriptor) EncodeBinary(w *io.BinWriter) {
w.WriteString(s.Field) w.WriteString(s.Field)
w.WriteVarBytes(s.Value) w.WriteVarBytes(s.Value)
} }
// stateDescriptor is a wrapper for StateDescriptor
type stateDescriptor struct {
Type DescStateType `json:"type"`
Key string `json:"key"`
Value string `json:"value"`
Field string `json:"field"`
}
// MarshalJSON implements json.Marshaler interface.
func (s *StateDescriptor) MarshalJSON() ([]byte, error) {
return json.Marshal(&stateDescriptor{
Type: s.Type,
Key: hex.EncodeToString(s.Key),
Value: hex.EncodeToString(s.Value),
Field: s.Field,
})
}
// UnmarshalJSON implements json.Unmarshaler interface.
func (s *StateDescriptor) UnmarshalJSON(data []byte) error {
t := new(stateDescriptor)
if err := json.Unmarshal(data, t); err != nil {
return err
}
key, err := hex.DecodeString(t.Key)
if err != nil {
return err
}
value, err := hex.DecodeString(t.Value)
if err != nil {
return err
}
s.Key = key
s.Value = value
s.Field = t.Field
s.Type = t.Type
return nil
}

View file

@ -1,10 +1,14 @@
package transaction package transaction
import ( import (
"encoding/hex"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
) )
@ -18,28 +22,28 @@ const (
// Transaction is a process recorded in the NEO blockchain. // Transaction is a process recorded in the NEO blockchain.
type Transaction struct { type Transaction struct {
// The type of the transaction. // The type of the transaction.
Type TXType `json:"type"` Type TXType
// The trading version which is currently 0. // The trading version which is currently 0.
Version uint8 `json:"version"` Version uint8
// Data specific to the type of the transaction. // Data specific to the type of the transaction.
// This is always a pointer to a <Type>Transaction. // This is always a pointer to a <Type>Transaction.
Data TXer `json:"-"` Data TXer
// Transaction attributes. // Transaction attributes.
Attributes []Attribute `json:"attributes"` Attributes []Attribute
// The inputs of the transaction. // The inputs of the transaction.
Inputs []Input `json:"vin"` Inputs []Input
// The outputs of the transaction. // The outputs of the transaction.
Outputs []Output `json:"vout"` Outputs []Output
// The scripts that comes with this transaction. // The scripts that comes with this transaction.
// Scripts exist out of the verification script // Scripts exist out of the verification script
// and invocation script. // and invocation script.
Scripts []Witness `json:"scripts"` Scripts []Witness
// Hash of the transaction (double SHA256). // Hash of the transaction (double SHA256).
hash util.Uint256 hash util.Uint256
@ -49,7 +53,7 @@ type Transaction struct {
// Trimmed indicates this is a transaction from trimmed // Trimmed indicates this is a transaction from trimmed
// data. // data.
Trimmed bool `json:"-"` Trimmed bool
} }
// NewTrimmedTX returns a trimmed transaction with only its hash // NewTrimmedTX returns a trimmed transaction with only its hash
@ -233,3 +237,160 @@ func (t *Transaction) Bytes() []byte {
} }
return buf.Bytes() return buf.Bytes()
} }
// transactionJSON is a wrapper for Transaction and
// used for correct marhalling of transaction.Data
type transactionJSON struct {
TxID util.Uint256 `json:"txid"`
Size int `json:"size"`
Type TXType `json:"type"`
Version uint8 `json:"version"`
Attributes []Attribute `json:"attributes"`
Inputs []Input `json:"vin"`
Outputs []Output `json:"vout"`
Scripts []Witness `json:"scripts"`
Claims []Input `json:"claims,omitempty"`
PublicKey *keys.PublicKey `json:"pubkey,omitempty"`
Script string `json:"script,omitempty"`
Gas util.Fixed8 `json:"gas,omitempty"`
Nonce uint32 `json:"nonce,omitempty"`
Contract *publishedContract `json:"contract,omitempty"`
Asset *registeredAsset `json:"asset,omitempty"`
Descriptors []*StateDescriptor `json:"descriptors,omitempty"`
}
// MarshalJSON implements json.Marshaler interface.
func (t *Transaction) MarshalJSON() ([]byte, error) {
tx := transactionJSON{
TxID: t.Hash(),
Size: io.GetVarSize(t),
Type: t.Type,
Version: t.Version,
Attributes: t.Attributes,
Inputs: t.Inputs,
Outputs: t.Outputs,
Scripts: t.Scripts,
}
switch t.Type {
case MinerType:
tx.Nonce = t.Data.(*MinerTX).Nonce
case ClaimType:
tx.Claims = t.Data.(*ClaimTX).Claims
case EnrollmentType:
tx.PublicKey = &t.Data.(*EnrollmentTX).PublicKey
case InvocationType:
tx.Script = hex.EncodeToString(t.Data.(*InvocationTX).Script)
tx.Gas = t.Data.(*InvocationTX).Gas
case PublishType:
transaction := t.Data.(*PublishTX)
tx.Contract = &publishedContract{
Code: publishedCode{
Hash: hash.Hash160(transaction.Script),
Script: hex.EncodeToString(transaction.Script),
ParamList: transaction.ParamList,
ReturnType: transaction.ReturnType,
},
NeedStorage: transaction.NeedStorage,
Name: transaction.Name,
CodeVersion: transaction.CodeVersion,
Author: transaction.Author,
Email: transaction.Email,
Description: transaction.Description,
}
case RegisterType:
transaction := *t.Data.(*RegisterTX)
tx.Asset = &registeredAsset{
AssetType: transaction.AssetType,
Name: json.RawMessage(transaction.Name),
Amount: transaction.Amount,
Precision: transaction.Precision,
Owner: transaction.Owner,
Admin: address.Uint160ToString(transaction.Admin),
}
case StateType:
tx.Descriptors = t.Data.(*StateTX).Descriptors
}
return json.Marshal(tx)
}
// UnmarshalJSON implements json.Unmarshaler interface.
func (t *Transaction) UnmarshalJSON(data []byte) error {
tx := new(transactionJSON)
if err := json.Unmarshal(data, tx); err != nil {
return err
}
t.Type = tx.Type
t.Version = tx.Version
t.Attributes = tx.Attributes
t.Inputs = tx.Inputs
t.Outputs = tx.Outputs
t.Scripts = tx.Scripts
switch tx.Type {
case MinerType:
t.Data = &MinerTX{
Nonce: tx.Nonce,
}
case ClaimType:
t.Data = &ClaimTX{
Claims: tx.Claims,
}
case EnrollmentType:
t.Data = &EnrollmentTX{
PublicKey: *tx.PublicKey,
}
case InvocationType:
bytes, err := hex.DecodeString(tx.Script)
if err != nil {
return err
}
t.Data = &InvocationTX{
Script: bytes,
Gas: tx.Gas,
Version: tx.Version,
}
case PublishType:
bytes, err := hex.DecodeString(tx.Contract.Code.Script)
if err != nil {
return err
}
t.Data = &PublishTX{
Script: bytes,
ParamList: tx.Contract.Code.ParamList,
ReturnType: tx.Contract.Code.ReturnType,
NeedStorage: tx.Contract.NeedStorage,
Name: tx.Contract.Name,
CodeVersion: tx.Contract.CodeVersion,
Author: tx.Contract.Author,
Email: tx.Contract.Email,
Description: tx.Contract.Description,
Version: tx.Version,
}
case RegisterType:
admin, err := address.StringToUint160(tx.Asset.Admin)
if err != nil {
return err
}
t.Data = &RegisterTX{
AssetType: tx.Asset.AssetType,
Name: string(tx.Asset.Name),
Amount: tx.Asset.Amount,
Precision: tx.Asset.Precision,
Owner: tx.Asset.Owner,
Admin: admin,
}
case StateType:
t.Data = &StateTX{
Descriptors: tx.Descriptors,
}
case ContractType:
t.Data = &ContractTX{}
case IssueType:
t.Data = &IssueTX{}
}
if t.Hash() != tx.TxID {
return errors.New("txid doesn't match transaction hash")
}
return nil
}

View file

@ -4,6 +4,7 @@ import (
"encoding/hex" "encoding/hex"
"testing" "testing"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/internal/testserdes" "github.com/nspcc-dev/neo-go/pkg/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
@ -155,7 +156,7 @@ func TestEncodingTXWithNoData(t *testing.T) {
require.Error(t, err) require.Error(t, err)
} }
func TestMarshalUnmarshalJSON(t *testing.T) { func TestMarshalUnmarshalJSONContractTX(t *testing.T) {
tx := NewContractTX() tx := NewContractTX()
tx.Outputs = []Output{{ tx.Outputs = []Output{{
AssetID: util.Uint256{1, 2, 3, 4}, AssetID: util.Uint256{1, 2, 3, 4},
@ -167,5 +168,196 @@ func TestMarshalUnmarshalJSON(t *testing.T) {
InvocationScript: []byte{5, 3, 1}, InvocationScript: []byte{5, 3, 1},
VerificationScript: []byte{2, 4, 6}, VerificationScript: []byte{2, 4, 6},
}} }}
tx.Data = &ContractTX{}
testserdes.MarshalUnmarshalJSON(t, tx, new(Transaction))
}
func TestMarshalUnmarshalJSONMinerTX(t *testing.T) {
tx := &Transaction{
Type: MinerType,
Version: 0,
Data: &MinerTX{Nonce: 12345},
Attributes: []Attribute{},
Inputs: []Input{},
Outputs: []Output{},
Scripts: []Witness{},
Trimmed: false,
}
testserdes.MarshalUnmarshalJSON(t, tx, new(Transaction))
}
func TestMarshalUnmarshalJSONClaimTX(t *testing.T) {
tx := &Transaction{
Type: ClaimType,
Version: 0,
Data: &ClaimTX{Claims: []Input{
{
PrevHash: util.Uint256{1, 2, 3, 4},
PrevIndex: uint16(56),
},
}},
Attributes: []Attribute{},
Inputs: []Input{{
PrevHash: util.Uint256{5, 6, 7, 8},
PrevIndex: uint16(12),
}},
Outputs: []Output{{
AssetID: util.Uint256{1, 2, 3},
Amount: util.Fixed8FromInt64(1),
ScriptHash: util.Uint160{1, 2, 3},
Position: 0,
}},
Scripts: []Witness{},
Trimmed: false,
}
testserdes.MarshalUnmarshalJSON(t, tx, new(Transaction))
}
func TestMarshalUnmarshalJSONEnrollmentTX(t *testing.T) {
str := "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"
pubKey, err := keys.NewPublicKeyFromString(str)
require.NoError(t, err)
tx := &Transaction{
Type: EnrollmentType,
Version: 5,
Data: &EnrollmentTX{PublicKey: *pubKey},
Attributes: []Attribute{},
Inputs: []Input{{
PrevHash: util.Uint256{5, 6, 7, 8},
PrevIndex: uint16(12),
}},
Outputs: []Output{{
AssetID: util.Uint256{1, 2, 3},
Amount: util.Fixed8FromInt64(1),
ScriptHash: util.Uint160{1, 2, 3},
Position: 0,
}},
Scripts: []Witness{},
Trimmed: false,
}
testserdes.MarshalUnmarshalJSON(t, tx, new(Transaction))
}
func TestMarshalUnmarshalJSONInvocationTX(t *testing.T) {
tx := &Transaction{
Type: InvocationType,
Version: 3,
Data: &InvocationTX{
Script: []byte{1, 2, 3, 4},
Gas: util.Fixed8FromFloat(100),
Version: 3,
},
Attributes: []Attribute{},
Inputs: []Input{{
PrevHash: util.Uint256{5, 6, 7, 8},
PrevIndex: uint16(12),
}},
Outputs: []Output{{
AssetID: util.Uint256{1, 2, 3},
Amount: util.Fixed8FromInt64(1),
ScriptHash: util.Uint160{1, 2, 3},
Position: 0,
}},
Scripts: []Witness{},
Trimmed: false,
}
testserdes.MarshalUnmarshalJSON(t, tx, new(Transaction))
}
func TestMarshalUnmarshalJSONPublishTX(t *testing.T) {
tx := &Transaction{
Type: PublishType,
Version: 5,
Data: &PublishTX{
Script: []byte{1, 2, 3, 4},
ParamList: []smartcontract.ParamType{smartcontract.IntegerType, smartcontract.Hash160Type},
ReturnType: smartcontract.BoolType,
NeedStorage: true,
Name: "Name",
CodeVersion: "1.0",
Author: "Author",
Email: "Email",
Description: "Description",
Version: 5,
},
Attributes: []Attribute{},
Inputs: []Input{{
PrevHash: util.Uint256{5, 6, 7, 8},
PrevIndex: uint16(12),
}},
Outputs: []Output{{
AssetID: util.Uint256{1, 2, 3},
Amount: util.Fixed8FromInt64(1),
ScriptHash: util.Uint160{1, 2, 3},
Position: 0,
}},
Scripts: []Witness{},
Trimmed: false,
}
testserdes.MarshalUnmarshalJSON(t, tx, new(Transaction))
}
func TestMarshalUnmarshalJSONRegisterTX(t *testing.T) {
tx := &Transaction{
Type: RegisterType,
Version: 5,
Data: &RegisterTX{
AssetType: 0,
Name: `[{"lang":"zh-CN","name":"小蚁股"},{"lang":"en","name":"AntShare"}]`,
Amount: 1000000,
Precision: 0,
Owner: keys.PublicKey{},
Admin: util.Uint160{},
},
Attributes: []Attribute{},
Inputs: []Input{{
PrevHash: util.Uint256{5, 6, 7, 8},
PrevIndex: uint16(12),
}},
Outputs: []Output{{
AssetID: util.Uint256{1, 2, 3},
Amount: util.Fixed8FromInt64(1),
ScriptHash: util.Uint160{1, 2, 3},
Position: 0,
}},
Scripts: []Witness{},
Trimmed: false,
}
testserdes.MarshalUnmarshalJSON(t, tx, new(Transaction))
}
func TestMarshalUnmarshalJSONStateTX(t *testing.T) {
tx := &Transaction{
Type: StateType,
Version: 5,
Data: &StateTX{
Descriptors: []*StateDescriptor{&StateDescriptor{
Type: Validator,
Key: []byte{1, 2, 3},
Value: []byte{4, 5, 6},
Field: "Field",
}},
},
Attributes: []Attribute{},
Inputs: []Input{{
PrevHash: util.Uint256{5, 6, 7, 8},
PrevIndex: uint16(12),
}},
Outputs: []Output{{
AssetID: util.Uint256{1, 2, 3},
Amount: util.Fixed8FromInt64(1),
ScriptHash: util.Uint160{1, 2, 3},
Position: 0,
}},
Scripts: []Witness{},
Trimmed: false,
}
testserdes.MarshalUnmarshalJSON(t, tx, new(Transaction)) testserdes.MarshalUnmarshalJSON(t, tx, new(Transaction))
} }

View file

@ -104,6 +104,7 @@ func (c *Client) getBlock(params request.RawParams) (*block.Block, error) {
// GetBlockByIndexVerbose returns a block wrapper with additional metadata by // GetBlockByIndexVerbose returns a block wrapper with additional metadata by
// its height. // its height.
// NOTE: to get transaction.ID and transaction.Size, use t.Hash() and io.GetVarSize(t) respectively.
func (c *Client) GetBlockByIndexVerbose(index uint32) (*result.Block, error) { func (c *Client) GetBlockByIndexVerbose(index uint32) (*result.Block, error) {
return c.getBlockVerbose(request.NewRawParams(index, 1)) return c.getBlockVerbose(request.NewRawParams(index, 1))
} }
@ -289,6 +290,7 @@ func (c *Client) GetRawTransaction(hash util.Uint256) (*transaction.Transaction,
// GetRawTransactionVerbose returns a transaction wrapper with additional // GetRawTransactionVerbose returns a transaction wrapper with additional
// metadata by transaction's hash. // metadata by transaction's hash.
// NOTE: to get transaction.ID and transaction.Size, use t.Hash() and io.GetVarSize(t) respectively.
func (c *Client) GetRawTransactionVerbose(hash util.Uint256) (*result.TransactionOutputRaw, error) { func (c *Client) GetRawTransactionVerbose(hash util.Uint256) (*result.TransactionOutputRaw, error) {
var ( var (
params = request.NewRawParams(hash.StringLE(), 1) params = request.NewRawParams(hash.StringLE(), 1)

View file

@ -172,10 +172,6 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
if err != nil { if err != nil {
panic(err) panic(err)
} }
txID, err := util.Uint256DecodeStringLE("cb6ddb5f99d6af4c94a6c396d5294472f2eebc91a2c933e0f527422296fa9fb2")
if err != nil {
panic(err)
}
invScript, err := hex.DecodeString("40356a91d94e398170e47447d6a0f60aa5470e209782a5452403115a49166db3e1c4a3898122db19f779c30f8ccd0b7d401acdf71eda340655e4ae5237a64961bf4034dd47955e5a71627dafc39dd92999140e9eaeec6b11dbb2b313efa3f1093ed915b4455e199c69ec53778f94ffc236b92f8b97fff97a1f6bbb3770c0c0b3844a40fbe743bd5c90b2f5255e0b073281d7aeb2fb516572f36bec8446bcc37ac755cbf10d08b16c95644db1b2dddc2df5daa377880b20198fc7b967ac6e76474b22df") invScript, err := hex.DecodeString("40356a91d94e398170e47447d6a0f60aa5470e209782a5452403115a49166db3e1c4a3898122db19f779c30f8ccd0b7d401acdf71eda340655e4ae5237a64961bf4034dd47955e5a71627dafc39dd92999140e9eaeec6b11dbb2b313efa3f1093ed915b4455e199c69ec53778f94ffc236b92f8b97fff97a1f6bbb3770c0c0b3844a40fbe743bd5c90b2f5255e0b073281d7aeb2fb516572f36bec8446bcc37ac755cbf10d08b16c95644db1b2dddc2df5daa377880b20198fc7b967ac6e76474b22df")
if err != nil { if err != nil {
panic(err) panic(err)
@ -184,6 +180,18 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
if err != nil { if err != nil {
panic(err) panic(err)
} }
tx := &transaction.Transaction{
Type: transaction.MinerType,
Version: 0,
Data: &transaction.MinerTX{Nonce: 4266257741},
Attributes: []transaction.Attribute{},
Inputs: []transaction.Input{},
Outputs: []transaction.Output{},
Scripts: []transaction.Witness{},
Trimmed: false,
}
// Update hashes for correct result comparison.
_ = tx.Hash()
return &result.Block{ return &result.Block{
Hash: hash, Hash: hash,
Size: 452, Size: 452,
@ -201,17 +209,11 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
VerificationScript: verifScript, VerificationScript: verifScript,
}, },
Tx: []result.Tx{{ Tx: []result.Tx{{
TxID: txID, Transaction: tx,
Size: 10, Fees: result.Fees{
Type: transaction.MinerType, SysFee: 0,
Version: 0, NetFee: 0,
Attributes: []transaction.Attribute{}, },
VIn: []transaction.Input{},
VOut: []transaction.Output{},
Scripts: []transaction.Witness{},
SysFee: 0,
NetFee: 0,
Nonce: 4266257741,
}}, }},
} }
}, },
@ -265,10 +267,6 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
if err != nil { if err != nil {
panic(err) panic(err)
} }
txID, err := util.Uint256DecodeStringLE("cb6ddb5f99d6af4c94a6c396d5294472f2eebc91a2c933e0f527422296fa9fb2")
if err != nil {
panic(err)
}
invScript, err := hex.DecodeString("40356a91d94e398170e47447d6a0f60aa5470e209782a5452403115a49166db3e1c4a3898122db19f779c30f8ccd0b7d401acdf71eda340655e4ae5237a64961bf4034dd47955e5a71627dafc39dd92999140e9eaeec6b11dbb2b313efa3f1093ed915b4455e199c69ec53778f94ffc236b92f8b97fff97a1f6bbb3770c0c0b3844a40fbe743bd5c90b2f5255e0b073281d7aeb2fb516572f36bec8446bcc37ac755cbf10d08b16c95644db1b2dddc2df5daa377880b20198fc7b967ac6e76474b22df") invScript, err := hex.DecodeString("40356a91d94e398170e47447d6a0f60aa5470e209782a5452403115a49166db3e1c4a3898122db19f779c30f8ccd0b7d401acdf71eda340655e4ae5237a64961bf4034dd47955e5a71627dafc39dd92999140e9eaeec6b11dbb2b313efa3f1093ed915b4455e199c69ec53778f94ffc236b92f8b97fff97a1f6bbb3770c0c0b3844a40fbe743bd5c90b2f5255e0b073281d7aeb2fb516572f36bec8446bcc37ac755cbf10d08b16c95644db1b2dddc2df5daa377880b20198fc7b967ac6e76474b22df")
if err != nil { if err != nil {
panic(err) panic(err)
@ -277,6 +275,18 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
if err != nil { if err != nil {
panic(err) panic(err)
} }
tx := &transaction.Transaction{
Type: transaction.MinerType,
Version: 0,
Data: &transaction.MinerTX{Nonce: 4266257741},
Attributes: []transaction.Attribute{},
Inputs: []transaction.Input{},
Outputs: []transaction.Output{},
Scripts: []transaction.Witness{},
Trimmed: false,
}
// Update hashes for correct result comparison.
_ = tx.Hash()
return &result.Block{ return &result.Block{
Hash: hash, Hash: hash,
Size: 452, Size: 452,
@ -294,17 +304,11 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
VerificationScript: verifScript, VerificationScript: verifScript,
}, },
Tx: []result.Tx{{ Tx: []result.Tx{{
TxID: txID, Transaction: tx,
Size: 10, Fees: result.Fees{
Type: transaction.MinerType, SysFee: 0,
Version: 0, NetFee: 0,
Attributes: []transaction.Attribute{}, },
VIn: []transaction.Input{},
VOut: []transaction.Output{},
Scripts: []transaction.Witness{},
SysFee: 0,
NetFee: 0,
Nonce: 4266257741,
}}, }},
} }
}, },
@ -646,34 +650,34 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
} }
return c.GetRawTransactionVerbose(hash) return c.GetRawTransactionVerbose(hash)
}, },
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"type":"MinerTransaction","version":0,"attributes":[],"vin":[],"vout":[],"scripts":[],"txid":"0xcb6ddb5f99d6af4c94a6c396d5294472f2eebc91a2c933e0f527422296fa9fb2","size":10,"sys_fee":"0","net_fee":"0","blockhash":"0xe93d17a52967f9e69314385482bf86f85260e811b46bf4d4b261a7f4135a623c","confirmations":20875,"blocktime":1541215200}}`, serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"nonce":4266257741,"type":"MinerTransaction","version":0,"attributes":[],"vin":[],"vout":[],"scripts":[],"txid":"0xcb6ddb5f99d6af4c94a6c396d5294472f2eebc91a2c933e0f527422296fa9fb2","size":10,"sys_fee":"0","net_fee":"0","blockhash":"0xe93d17a52967f9e69314385482bf86f85260e811b46bf4d4b261a7f4135a623c","confirmations":20875,"blocktime":1541215200}}`,
result: func(c *Client) interface{} { result: func(c *Client) interface{} {
txHash, err := util.Uint256DecodeStringLE("cb6ddb5f99d6af4c94a6c396d5294472f2eebc91a2c933e0f527422296fa9fb2")
if err != nil {
panic(err)
}
blockHash, err := util.Uint256DecodeStringLE("e93d17a52967f9e69314385482bf86f85260e811b46bf4d4b261a7f4135a623c") blockHash, err := util.Uint256DecodeStringLE("e93d17a52967f9e69314385482bf86f85260e811b46bf4d4b261a7f4135a623c")
if err != nil { if err != nil {
panic(err) panic(err)
} }
tx := &transaction.Transaction{
Type: transaction.MinerType,
Version: 0,
Data: &transaction.MinerTX{Nonce: 4266257741},
Attributes: []transaction.Attribute{},
Inputs: []transaction.Input{},
Outputs: []transaction.Output{},
Scripts: []transaction.Witness{},
Trimmed: false,
}
// Update hashes for correct result comparison.
_ = tx.Hash()
return &result.TransactionOutputRaw{ return &result.TransactionOutputRaw{
Transaction: &transaction.Transaction{ Transaction: tx,
Type: transaction.MinerType, TransactionMetadata: result.TransactionMetadata{
Version: 0, SysFee: 0,
Data: nil, NetFee: 0,
Attributes: []transaction.Attribute{}, Blockhash: blockHash,
Inputs: []transaction.Input{}, Confirmations: 20875,
Outputs: []transaction.Output{}, Timestamp: uint32(1541215200),
Scripts: []transaction.Witness{},
Trimmed: false,
}, },
TxHash: txHash,
Size: 10,
SysFee: 0,
NetFee: 0,
Blockhash: blockHash,
Confirmations: 20875,
Timestamp: 1541215200,
} }
}, },
}, },

View file

@ -1,6 +1,8 @@
package result package result
import ( import (
"encoding/json"
"errors"
"fmt" "fmt"
"github.com/nspcc-dev/neo-go/pkg/core" "github.com/nspcc-dev/neo-go/pkg/core"
@ -15,19 +17,14 @@ type (
// Tx wrapper used for the representation of // Tx wrapper used for the representation of
// transaction on the RPC Server. // transaction on the RPC Server.
Tx struct { Tx struct {
TxID util.Uint256 `json:"txid"` *transaction.Transaction
Size int `json:"size"` Fees
Type transaction.TXType `json:"type"` }
Version uint8 `json:"version"`
Attributes []transaction.Attribute `json:"attributes"`
VIn []transaction.Input `json:"vin"`
VOut []transaction.Output `json:"vout"`
Scripts []transaction.Witness `json:"scripts"`
// Fees is an auxilliary struct for proper Tx marshaling.
Fees struct {
SysFee util.Fixed8 `json:"sys_fee"` SysFee util.Fixed8 `json:"sys_fee"`
NetFee util.Fixed8 `json:"net_fee"` NetFee util.Fixed8 `json:"net_fee"`
Nonce uint32 `json:"nonce,omitempty"`
} }
// Block wrapper used for the representation of // Block wrapper used for the representation of
@ -77,32 +74,59 @@ func NewBlock(b *block.Block, chain core.Blockchainer) Block {
} }
for i := range b.Transactions { for i := range b.Transactions {
tx := Tx{ res.Tx = append(res.Tx, Tx{
TxID: b.Transactions[i].Hash(), Transaction: b.Transactions[i],
Size: io.GetVarSize(b.Transactions[i]), Fees: Fees{
Type: b.Transactions[i].Type, SysFee: chain.SystemFee(b.Transactions[i]),
Version: b.Transactions[i].Version, NetFee: chain.NetworkFee(b.Transactions[i]),
Attributes: make([]transaction.Attribute, 0, len(b.Transactions[i].Attributes)), },
VIn: make([]transaction.Input, 0, len(b.Transactions[i].Inputs)), })
VOut: make([]transaction.Output, 0, len(b.Transactions[i].Outputs)),
Scripts: make([]transaction.Witness, 0, len(b.Transactions[i].Scripts)),
}
copy(tx.Attributes, b.Transactions[i].Attributes)
copy(tx.VIn, b.Transactions[i].Inputs)
copy(tx.VOut, b.Transactions[i].Outputs)
copy(tx.Scripts, b.Transactions[i].Scripts)
tx.SysFee = chain.SystemFee(b.Transactions[i])
tx.NetFee = chain.NetworkFee(b.Transactions[i])
// set nonce only for MinerTransaction
if miner, ok := b.Transactions[i].Data.(*transaction.MinerTX); ok {
tx.Nonce = miner.Nonce
}
res.Tx = append(res.Tx, tx)
} }
return res return res
} }
// MarshalJSON implements json.Marshaler interface.
func (t Tx) MarshalJSON() ([]byte, error) {
output, err := json.Marshal(&Fees{
SysFee: t.SysFee,
NetFee: t.NetFee,
})
if err != nil {
return nil, err
}
txBytes, err := json.Marshal(t.Transaction)
if err != nil {
return nil, err
}
// We have to keep both transaction.Transaction and tx at the same level in json in order to match C# API,
// so there's no way to marshall Tx correctly with standard json.Marshaller tool.
if output[len(output)-1] != '}' || txBytes[0] != '{' {
return nil, errors.New("can't merge internal jsons")
}
output[len(output)-1] = ','
output = append(output, txBytes[1:]...)
return output, nil
}
// UnmarshalJSON implements json.Marshaler interface.
func (t *Tx) UnmarshalJSON(data []byte) error {
// As transaction.Transaction and tx are at the same level in json, do unmarshalling
// separately for both structs.
output := new(Fees)
err := json.Unmarshal(data, output)
if err != nil {
return err
}
t.SysFee = output.SysFee
t.NetFee = output.NetFee
transaction := new(transaction.Transaction)
err = json.Unmarshal(data, transaction)
if err != nil {
return err
}
t.Transaction = transaction
return nil
}

View file

@ -1,10 +1,12 @@
package result package result
import ( import (
"encoding/json"
"errors"
"github.com/nspcc-dev/neo-go/pkg/core" "github.com/nspcc-dev/neo-go/pkg/core"
"github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
) )
@ -12,8 +14,11 @@ import (
// a Transaction. // a Transaction.
type TransactionOutputRaw struct { type TransactionOutputRaw struct {
*transaction.Transaction *transaction.Transaction
TxHash util.Uint256 `json:"txid"` TransactionMetadata
Size int `json:"size"` }
// TransactionMetadata is an auxilliary struct for proper TransactionOutputRaw marshaling.
type TransactionMetadata struct {
SysFee util.Fixed8 `json:"sys_fee"` SysFee util.Fixed8 `json:"sys_fee"`
NetFee util.Fixed8 `json:"net_fee"` NetFee util.Fixed8 `json:"net_fee"`
Blockhash util.Uint256 `json:"blockhash,omitempty"` Blockhash util.Uint256 `json:"blockhash,omitempty"`
@ -30,13 +35,64 @@ func NewTransactionOutputRaw(tx *transaction.Transaction, header *block.Header,
o.Position = i o.Position = i
} }
return TransactionOutputRaw{ return TransactionOutputRaw{
Transaction: tx, Transaction: tx,
TxHash: tx.Hash(), TransactionMetadata: TransactionMetadata{
Size: io.GetVarSize(tx), SysFee: chain.SystemFee(tx),
SysFee: chain.SystemFee(tx), NetFee: chain.NetworkFee(tx),
NetFee: chain.NetworkFee(tx), Blockhash: header.Hash(),
Blockhash: header.Hash(), Confirmations: confirmations,
Confirmations: confirmations, Timestamp: header.Timestamp,
Timestamp: header.Timestamp, },
} }
} }
// MarshalJSON implements json.Marshaler interface.
func (t TransactionOutputRaw) MarshalJSON() ([]byte, error) {
output, err := json.Marshal(TransactionMetadata{
SysFee: t.SysFee,
NetFee: t.NetFee,
Blockhash: t.Blockhash,
Confirmations: t.Confirmations,
Timestamp: t.Timestamp,
})
if err != nil {
return nil, err
}
txBytes, err := json.Marshal(t.Transaction)
if err != nil {
return nil, err
}
// We have to keep both transaction.Transaction and tranactionOutputRaw at the same level in json
// in order to match C# API, so there's no way to marshall Tx correctly with standard json.Marshaller tool.
if output[len(output)-1] != '}' || txBytes[0] != '{' {
return nil, errors.New("can't merge internal jsons")
}
output[len(output)-1] = ','
output = append(output, txBytes[1:]...)
return output, nil
}
// UnmarshalJSON implements json.Marshaler interface.
func (t *TransactionOutputRaw) UnmarshalJSON(data []byte) error {
// As transaction.Transaction and tranactionOutputRaw are at the same level in json,
// do unmarshalling separately for both structs.
output := new(TransactionMetadata)
err := json.Unmarshal(data, output)
if err != nil {
return err
}
t.SysFee = output.SysFee
t.NetFee = output.NetFee
t.Blockhash = output.Blockhash
t.Confirmations = output.Confirmations
t.Timestamp = output.Timestamp
transaction := new(transaction.Transaction)
err = json.Unmarshal(data, transaction)
if err != nil {
return err
}
t.Transaction = transaction
return nil
}

View file

@ -15,6 +15,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core" "github.com/nspcc-dev/neo-go/pkg/core"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/internal/random" "github.com/nspcc-dev/neo-go/pkg/internal/random"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
@ -336,12 +337,12 @@ var rpcTestCases = map[string][]rpcTestCase{
assert.Equal(t, block.Hash(), res.Hash) assert.Equal(t, block.Hash(), res.Hash)
for i := range res.Tx { for i := range res.Tx {
tx := res.Tx[i] tx := res.Tx[i]
require.Equal(t, transaction.MinerType, tx.Type) require.Equal(t, transaction.MinerType, tx.Transaction.Type)
miner, ok := block.Transactions[i].Data.(*transaction.MinerTX) miner, ok := block.Transactions[i].Data.(*transaction.MinerTX)
require.True(t, ok) require.True(t, ok)
require.Equal(t, miner.Nonce, tx.Nonce) require.Equal(t, miner.Nonce, tx.Transaction.Data.(*transaction.MinerTX).Nonce)
require.Equal(t, block.Transactions[i].Hash(), tx.TxID) require.Equal(t, block.Transactions[i].Hash(), tx.Transaction.Hash())
} }
}, },
}, },
@ -935,6 +936,31 @@ func TestRPC(t *testing.T) {
assert.Equal(t, "400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000", res) assert.Equal(t, "400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000", res)
}) })
t.Run("getrawtransaction 2 arguments, verbose", func(t *testing.T) {
block, _ := chain.GetBlock(chain.GetHeaderHash(0))
TXHash := block.Transactions[1].Hash()
rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s", 1]}"`, TXHash.StringLE())
body := doRPCCall(rpc, handler, t)
txOut := checkErrGetResult(t, body, false)
actual := result.TransactionOutputRaw{}
err := json.Unmarshal(txOut, &actual)
require.NoErrorf(t, err, "could not parse response: %s", txOut)
admin, err := util.Uint160DecodeStringBE("da1745e9b549bd0bfa1a569971c77eba30cd5a4b")
require.NoError(t, err)
assert.Equal(t, transaction.RegisterType, actual.Transaction.Type)
assert.Equal(t, &transaction.RegisterTX{
AssetType: 0,
Name: `[{"lang":"zh-CN","name":"小蚁股"},{"lang":"en","name":"AntShare"}]`,
Amount: util.Fixed8FromInt64(100000000),
Precision: 0,
Owner: keys.PublicKey{},
Admin: admin,
}, actual.Transaction.Data.(*transaction.RegisterTX))
assert.Equal(t, 210, actual.Confirmations)
assert.Equal(t, TXHash, actual.Transaction.Hash())
})
t.Run("gettxout", func(t *testing.T) { t.Run("gettxout", func(t *testing.T) {
block, _ := chain.GetBlock(chain.GetHeaderHash(0)) block, _ := chain.GetBlock(chain.GetHeaderHash(0))
tx := block.Transactions[3] tx := block.Transactions[3]