forked from TrueCloudLab/neoneo-go
d9400800e3
It essentialy is the new SystemFee, so use that. Had to increase GAS transfer in test chain to 1000 to pay for deployment.
416 lines
10 KiB
Go
416 lines
10 KiB
Go
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/encoding/address"
|
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
)
|
|
|
|
const (
|
|
// MaxTransactionSize is the upper limit size in bytes that a transaction can reach. It is
|
|
// set to be 102400.
|
|
MaxTransactionSize = 102400
|
|
// MaxValidUntilBlockIncrement is the upper increment size of blockhain height in blocs after
|
|
// exceeding that a transaction should fail validation. It is set to be 2102400.
|
|
MaxValidUntilBlockIncrement = 2102400
|
|
// MaxCosigners is maximum number of cosigners that can be contained within a transaction.
|
|
// It is set to be 16.
|
|
MaxCosigners = 16
|
|
)
|
|
|
|
// Transaction is a process recorded in the NEO blockchain.
|
|
type Transaction struct {
|
|
// The type of the transaction.
|
|
Type TXType
|
|
|
|
// The trading version which is currently 0.
|
|
Version uint8
|
|
|
|
// Random number to avoid hash collision.
|
|
Nonce uint32
|
|
|
|
// Address signed the transaction.
|
|
Sender util.Uint160
|
|
|
|
// Fee to be burned.
|
|
SystemFee util.Fixed8
|
|
|
|
// Fee to be distributed to consensus nodes.
|
|
NetworkFee util.Fixed8
|
|
|
|
// Maximum blockchain height exceeding which
|
|
// transaction should fail verification.
|
|
ValidUntilBlock uint32
|
|
|
|
// Data specific to the type of the transaction.
|
|
// This is always a pointer to a <Type>Transaction.
|
|
Data TXer
|
|
|
|
// Transaction attributes.
|
|
Attributes []Attribute
|
|
|
|
// Transaction cosigners (not include Sender).
|
|
Cosigners []Cosigner
|
|
|
|
// The inputs of the transaction.
|
|
Inputs []Input
|
|
|
|
// The outputs of the transaction.
|
|
Outputs []Output
|
|
|
|
// The scripts that comes with this transaction.
|
|
// Scripts exist out of the verification script
|
|
// and invocation script.
|
|
Scripts []Witness
|
|
|
|
// Hash of the transaction (double SHA256).
|
|
hash util.Uint256
|
|
|
|
// Hash of the transaction used to verify it (single SHA256).
|
|
verificationHash util.Uint256
|
|
|
|
// Trimmed indicates this is a transaction from trimmed
|
|
// data.
|
|
Trimmed bool
|
|
}
|
|
|
|
// NewTrimmedTX returns a trimmed transaction with only its hash
|
|
// and Trimmed to true.
|
|
func NewTrimmedTX(hash util.Uint256) *Transaction {
|
|
return &Transaction{
|
|
hash: hash,
|
|
Trimmed: true,
|
|
}
|
|
}
|
|
|
|
// Hash returns the hash of the transaction.
|
|
func (t *Transaction) Hash() util.Uint256 {
|
|
if t.hash.Equals(util.Uint256{}) {
|
|
if t.createHash() != nil {
|
|
panic("failed to compute hash!")
|
|
}
|
|
}
|
|
return t.hash
|
|
}
|
|
|
|
// VerificationHash returns the hash of the transaction used to verify it.
|
|
func (t *Transaction) VerificationHash() util.Uint256 {
|
|
if t.verificationHash.Equals(util.Uint256{}) {
|
|
if t.createHash() != nil {
|
|
panic("failed to compute hash!")
|
|
}
|
|
}
|
|
return t.verificationHash
|
|
}
|
|
|
|
// AddOutput adds the given output to the transaction outputs.
|
|
func (t *Transaction) AddOutput(out *Output) {
|
|
t.Outputs = append(t.Outputs, *out)
|
|
}
|
|
|
|
// AddInput adds the given input to the transaction inputs.
|
|
func (t *Transaction) AddInput(in *Input) {
|
|
t.Inputs = append(t.Inputs, *in)
|
|
}
|
|
|
|
// DecodeBinary implements Serializable interface.
|
|
func (t *Transaction) DecodeBinary(br *io.BinReader) {
|
|
t.Type = TXType(br.ReadB())
|
|
t.Version = uint8(br.ReadB())
|
|
t.Nonce = br.ReadU32LE()
|
|
t.Sender.DecodeBinary(br)
|
|
t.SystemFee.DecodeBinary(br)
|
|
if t.SystemFee < 0 {
|
|
br.Err = errors.New("negative system fee")
|
|
return
|
|
}
|
|
t.NetworkFee.DecodeBinary(br)
|
|
if t.NetworkFee < 0 {
|
|
br.Err = errors.New("negative network fee")
|
|
return
|
|
}
|
|
if t.NetworkFee+t.SystemFee < t.SystemFee {
|
|
br.Err = errors.New("too big fees: int 64 overflow")
|
|
return
|
|
}
|
|
t.ValidUntilBlock = br.ReadU32LE()
|
|
t.decodeData(br)
|
|
|
|
br.ReadArray(&t.Attributes)
|
|
|
|
br.ReadArray(&t.Cosigners, MaxCosigners)
|
|
for i := 0; i < len(t.Cosigners); i++ {
|
|
for j := i + 1; j < len(t.Cosigners); j++ {
|
|
if t.Cosigners[i].Account.Equals(t.Cosigners[j].Account) {
|
|
br.Err = errors.New("transaction cosigners should be unique")
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
br.ReadArray(&t.Inputs)
|
|
br.ReadArray(&t.Outputs)
|
|
for i := range t.Outputs {
|
|
if t.Outputs[i].Amount.LessThan(0) {
|
|
br.Err = errors.New("negative output")
|
|
return
|
|
}
|
|
}
|
|
br.ReadArray(&t.Scripts)
|
|
|
|
// Create the hash of the transaction at decode, so we dont need
|
|
// to do it anymore.
|
|
if br.Err == nil {
|
|
br.Err = t.createHash()
|
|
}
|
|
}
|
|
|
|
func (t *Transaction) decodeData(r *io.BinReader) {
|
|
switch t.Type {
|
|
case InvocationType:
|
|
t.Data = &InvocationTX{}
|
|
t.Data.(*InvocationTX).DecodeBinary(r)
|
|
case ClaimType:
|
|
t.Data = &ClaimTX{}
|
|
t.Data.(*ClaimTX).DecodeBinary(r)
|
|
case ContractType:
|
|
t.Data = &ContractTX{}
|
|
t.Data.(*ContractTX).DecodeBinary(r)
|
|
case RegisterType:
|
|
t.Data = &RegisterTX{}
|
|
t.Data.(*RegisterTX).DecodeBinary(r)
|
|
case IssueType:
|
|
t.Data = &IssueTX{}
|
|
t.Data.(*IssueTX).DecodeBinary(r)
|
|
default:
|
|
r.Err = fmt.Errorf("invalid TX type %x", t.Type)
|
|
}
|
|
}
|
|
|
|
// EncodeBinary implements Serializable interface.
|
|
func (t *Transaction) EncodeBinary(bw *io.BinWriter) {
|
|
t.encodeHashableFields(bw)
|
|
bw.WriteArray(t.Scripts)
|
|
}
|
|
|
|
// encodeHashableFields encodes the fields that are not used for
|
|
// signing the transaction, which are all fields except the scripts.
|
|
func (t *Transaction) encodeHashableFields(bw *io.BinWriter) {
|
|
noData := t.Type == ContractType
|
|
if t.Data == nil && !noData {
|
|
bw.Err = errors.New("transaction has no data")
|
|
return
|
|
}
|
|
bw.WriteB(byte(t.Type))
|
|
bw.WriteB(byte(t.Version))
|
|
bw.WriteU32LE(t.Nonce)
|
|
t.Sender.EncodeBinary(bw)
|
|
t.SystemFee.EncodeBinary(bw)
|
|
t.NetworkFee.EncodeBinary(bw)
|
|
bw.WriteU32LE(t.ValidUntilBlock)
|
|
|
|
// Underlying TXer.
|
|
if !noData {
|
|
t.Data.EncodeBinary(bw)
|
|
}
|
|
|
|
// Attributes
|
|
bw.WriteArray(t.Attributes)
|
|
|
|
// Cosigners
|
|
bw.WriteArray(t.Cosigners)
|
|
|
|
// Inputs
|
|
bw.WriteArray(t.Inputs)
|
|
|
|
// Outputs
|
|
bw.WriteArray(t.Outputs)
|
|
}
|
|
|
|
// createHash creates the hash of the transaction.
|
|
func (t *Transaction) createHash() error {
|
|
buf := io.NewBufBinWriter()
|
|
t.encodeHashableFields(buf.BinWriter)
|
|
if buf.Err != nil {
|
|
return buf.Err
|
|
}
|
|
|
|
b := buf.Bytes()
|
|
t.verificationHash = hash.Sha256(b)
|
|
t.hash = hash.Sha256(t.verificationHash.BytesBE())
|
|
|
|
return nil
|
|
}
|
|
|
|
// GroupOutputByAssetID groups all TX outputs by their assetID.
|
|
func (t Transaction) GroupOutputByAssetID() map[util.Uint256][]*Output {
|
|
m := make(map[util.Uint256][]*Output)
|
|
for i := range t.Outputs {
|
|
hash := t.Outputs[i].AssetID
|
|
m[hash] = append(m[hash], &t.Outputs[i])
|
|
}
|
|
return m
|
|
}
|
|
|
|
// GetSignedPart returns a part of the transaction which must be signed.
|
|
func (t *Transaction) GetSignedPart() []byte {
|
|
buf := io.NewBufBinWriter()
|
|
t.encodeHashableFields(buf.BinWriter)
|
|
if buf.Err != nil {
|
|
return nil
|
|
}
|
|
return buf.Bytes()
|
|
}
|
|
|
|
// Bytes converts the transaction to []byte
|
|
func (t *Transaction) Bytes() []byte {
|
|
buf := io.NewBufBinWriter()
|
|
t.EncodeBinary(buf.BinWriter)
|
|
if buf.Err != nil {
|
|
return nil
|
|
}
|
|
return buf.Bytes()
|
|
}
|
|
|
|
// NewTransactionFromBytes decodes byte array into *Transaction
|
|
func NewTransactionFromBytes(b []byte) (*Transaction, error) {
|
|
tx := &Transaction{}
|
|
r := io.NewBinReaderFromBuf(b)
|
|
tx.DecodeBinary(r)
|
|
if r.Err != nil {
|
|
return nil, r.Err
|
|
}
|
|
return tx, nil
|
|
}
|
|
|
|
// FeePerByte returns NetworkFee of the transaction divided by
|
|
// its size
|
|
func (t *Transaction) FeePerByte() util.Fixed8 {
|
|
return util.Fixed8(int64(t.NetworkFee) / int64(io.GetVarSize(t)))
|
|
}
|
|
|
|
// 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"`
|
|
Nonce uint32 `json:"nonce"`
|
|
Sender string `json:"sender"`
|
|
SystemFee util.Fixed8 `json:"sys_fee"`
|
|
NetworkFee util.Fixed8 `json:"net_fee"`
|
|
ValidUntilBlock uint32 `json:"valid_until_block"`
|
|
Attributes []Attribute `json:"attributes"`
|
|
Cosigners []Cosigner `json:"cosigners"`
|
|
Inputs []Input `json:"vin"`
|
|
Outputs []Output `json:"vout"`
|
|
Scripts []Witness `json:"scripts"`
|
|
|
|
Claims []Input `json:"claims,omitempty"`
|
|
Script string `json:"script,omitempty"`
|
|
Asset *registeredAsset `json:"asset,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,
|
|
Nonce: t.Nonce,
|
|
Sender: address.Uint160ToString(t.Sender),
|
|
ValidUntilBlock: t.ValidUntilBlock,
|
|
Attributes: t.Attributes,
|
|
Cosigners: t.Cosigners,
|
|
Inputs: t.Inputs,
|
|
Outputs: t.Outputs,
|
|
Scripts: t.Scripts,
|
|
SystemFee: t.SystemFee,
|
|
NetworkFee: t.NetworkFee,
|
|
}
|
|
switch t.Type {
|
|
case ClaimType:
|
|
tx.Claims = t.Data.(*ClaimTX).Claims
|
|
case InvocationType:
|
|
tx.Script = hex.EncodeToString(t.Data.(*InvocationTX).Script)
|
|
case RegisterType:
|
|
transaction := *t.Data.(*RegisterTX)
|
|
tx.Asset = ®isteredAsset{
|
|
AssetType: transaction.AssetType,
|
|
Name: json.RawMessage(transaction.Name),
|
|
Amount: transaction.Amount,
|
|
Precision: transaction.Precision,
|
|
Owner: transaction.Owner,
|
|
Admin: address.Uint160ToString(transaction.Admin),
|
|
}
|
|
}
|
|
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.Nonce = tx.Nonce
|
|
t.ValidUntilBlock = tx.ValidUntilBlock
|
|
t.Attributes = tx.Attributes
|
|
t.Cosigners = tx.Cosigners
|
|
t.Inputs = tx.Inputs
|
|
t.Outputs = tx.Outputs
|
|
t.Scripts = tx.Scripts
|
|
t.SystemFee = tx.SystemFee
|
|
t.NetworkFee = tx.NetworkFee
|
|
sender, err := address.StringToUint160(tx.Sender)
|
|
if err != nil {
|
|
return errors.New("cannot unmarshal tx: bad sender")
|
|
}
|
|
t.Sender = sender
|
|
switch tx.Type {
|
|
case ClaimType:
|
|
t.Data = &ClaimTX{
|
|
Claims: tx.Claims,
|
|
}
|
|
case InvocationType:
|
|
bytes, err := hex.DecodeString(tx.Script)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
t.Data = &InvocationTX{
|
|
Script: bytes,
|
|
}
|
|
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 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
|
|
}
|