neo-go/pkg/core/transaction/transaction.go
2020-06-05 19:20:16 +03:00

313 lines
8 KiB
Go

package transaction
import (
"encoding/hex"
"encoding/json"
"errors"
"math/rand"
"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 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
// Code to run in NeoVM for this transaction.
Script []byte
// Transaction attributes.
Attributes []Attribute
// Transaction cosigners (not include Sender).
Cosigners []Cosigner
// 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,
}
}
// New returns a new transaction to execute given script and pay given system
// fee.
func New(script []byte, gas util.Fixed8) *Transaction {
return &Transaction{
Version: 0,
Nonce: rand.Uint32(),
Script: script,
SystemFee: gas,
Attributes: []Attribute{},
Cosigners: []Cosigner{},
Scripts: []Witness{},
}
}
// 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
}
// DecodeBinary implements Serializable interface.
func (t *Transaction) DecodeBinary(br *io.BinReader) {
t.Version = uint8(br.ReadB())
if t.Version > 0 {
br.Err = errors.New("only version 0 is supported")
return
}
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()
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
}
}
}
t.Script = br.ReadVarBytes()
if br.Err == nil && len(t.Script) == 0 {
br.Err = errors.New("no script")
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()
}
}
// 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) {
if len(t.Script) == 0 {
bw.Err = errors.New("transaction has no script")
return
}
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)
// Attributes
bw.WriteArray(t.Attributes)
// Cosigners
bw.WriteArray(t.Cosigners)
bw.WriteVarBytes(t.Script)
}
// 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
}
// 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"`
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"`
Script string `json:"script"`
Scripts []Witness `json:"scripts"`
}
// MarshalJSON implements json.Marshaler interface.
func (t *Transaction) MarshalJSON() ([]byte, error) {
tx := transactionJSON{
TxID: t.Hash(),
Size: io.GetVarSize(t),
Version: t.Version,
Nonce: t.Nonce,
Sender: address.Uint160ToString(t.Sender),
ValidUntilBlock: t.ValidUntilBlock,
Attributes: t.Attributes,
Cosigners: t.Cosigners,
Script: hex.EncodeToString(t.Script),
Scripts: t.Scripts,
SystemFee: t.SystemFee,
NetworkFee: t.NetworkFee,
}
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.Version = tx.Version
t.Nonce = tx.Nonce
t.ValidUntilBlock = tx.ValidUntilBlock
t.Attributes = tx.Attributes
t.Cosigners = tx.Cosigners
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
t.Script, err = hex.DecodeString(tx.Script)
if err != nil {
return err
}
if t.Hash() != tx.TxID {
return errors.New("txid doesn't match transaction hash")
}
return nil
}