Skip to content

Commit

Permalink
rpc: fix marshalling of type-specific tx data
Browse files Browse the repository at this point in the history
closes #585
  • Loading branch information
AnnaShaleva committed Mar 27, 2020
1 parent ade8638 commit adf370a
Show file tree
Hide file tree
Showing 8 changed files with 638 additions and 340 deletions.
42 changes: 42 additions & 0 deletions pkg/core/transaction/state_descriptor.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package transaction

import (
"encoding/hex"
"encoding/json"

"github.com/nspcc-dev/neo-go/pkg/io"
)

Expand Down Expand Up @@ -37,3 +40,42 @@ func (s *StateDescriptor) EncodeBinary(w *io.BinWriter) {
w.WriteString(s.Field)
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
}
212 changes: 204 additions & 8 deletions pkg/core/transaction/transaction.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package transaction

import (
"encoding/hex"
"encoding/json"
"errors"
"fmt"

"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/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
)

Expand All @@ -18,28 +23,28 @@ const (
// Transaction is a process recorded in the NEO blockchain.
type Transaction struct {
// The type of the transaction.
Type TXType `json:"type"`
Type TXType

// The trading version which is currently 0.
Version uint8 `json:"version"`
Version uint8

// Data specific to the type of the transaction.
// This is always a pointer to a <Type>Transaction.
Data TXer `json:"-"`
Data TXer

// Transaction attributes.
Attributes []Attribute `json:"attributes"`
Attributes []Attribute

// The inputs of the transaction.
Inputs []Input `json:"vin"`
Inputs []Input

// The outputs of the transaction.
Outputs []Output `json:"vout"`
Outputs []Output

// The scripts that comes with this transaction.
// Scripts exist out of the verification script
// and invocation script.
Scripts []Witness `json:"scripts"`
Scripts []Witness

// Hash of the transaction (double SHA256).
hash util.Uint256
Expand All @@ -49,7 +54,7 @@ type Transaction struct {

// Trimmed indicates this is a transaction from trimmed
// data.
Trimmed bool `json:"-"`
Trimmed bool
}

// NewTrimmedTX returns a trimmed transaction with only its hash
Expand Down Expand Up @@ -233,3 +238,194 @@ func (t *Transaction) Bytes() []byte {
}
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 *contract `json:"contract,omitempty"`
Asset *asset `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 = &contract{
Code: code{
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 = &asset{
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: 1,
}
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: uint8(1),
}
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{}
}

err := t.createHash()
if err != nil {
return err
}
if t.Hash() != tx.TxID {
return errors.New("can not unmarshal transaction: bad transaction id")
}

return nil
}

// contract is a wrapper for PublishTransaction
type contract struct {
Code code `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"`
}

// code is a wrapper for PublishTransaction Code
type code struct {
Hash util.Uint160 `json:"hash,omitempty"`
Script string `json:"script,omitempty"`
ParamList []smartcontract.ParamType `json:"parameters,omitempty"`
ReturnType smartcontract.ParamType `json:"returntype,omitempty"`
}

// asset is a wrapper for RegisterTransaction
type asset 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"`
}
1 change: 1 addition & 0 deletions pkg/core/transaction/transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ func TestMarshalUnmarshalJSON(t *testing.T) {
InvocationScript: []byte{5, 3, 1},
VerificationScript: []byte{2, 4, 6},
}}
tx.Data = &ContractTX{}
data, err := json.Marshal(tx)
require.NoError(t, err)

Expand Down
Loading

0 comments on commit adf370a

Please sign in to comment.