github.com/dim4egster/coreth@v0.10.2/plugin/evm/tx.go (about)

     1  // (c) 2019-2020, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package evm
     5  
     6  import (
     7  	"bytes"
     8  	"errors"
     9  	"fmt"
    10  	"math/big"
    11  	"sort"
    12  
    13  	"github.com/ethereum/go-ethereum/common"
    14  
    15  	"github.com/dim4egster/coreth/core/state"
    16  	"github.com/dim4egster/coreth/params"
    17  
    18  	"github.com/dim4egster/qmallgo/chains/atomic"
    19  	"github.com/dim4egster/qmallgo/codec"
    20  	"github.com/dim4egster/qmallgo/ids"
    21  	"github.com/dim4egster/qmallgo/snow"
    22  	"github.com/dim4egster/qmallgo/utils"
    23  	"github.com/dim4egster/qmallgo/utils/crypto"
    24  	"github.com/dim4egster/qmallgo/utils/hashing"
    25  	"github.com/dim4egster/qmallgo/utils/wrappers"
    26  	"github.com/dim4egster/qmallgo/vms/components/verify"
    27  	"github.com/dim4egster/qmallgo/vms/secp256k1fx"
    28  )
    29  
    30  var (
    31  	errWrongBlockchainID = errors.New("wrong blockchain ID provided")
    32  	errWrongNetworkID    = errors.New("tx was issued with a different network ID")
    33  	errNilTx             = errors.New("tx is nil")
    34  	errNoValueOutput     = errors.New("output has no value")
    35  	errNoValueInput      = errors.New("input has no value")
    36  	errNilOutput         = errors.New("nil output")
    37  	errNilInput          = errors.New("nil input")
    38  	errEmptyAssetID      = errors.New("empty asset ID is not valid")
    39  	errNilBaseFee        = errors.New("cannot calculate dynamic fee with nil baseFee")
    40  	errFeeOverflow       = errors.New("overflow occurred while calculating the fee")
    41  )
    42  
    43  // Constants for calculating the gas consumed by atomic transactions
    44  var (
    45  	TxBytesGas   uint64 = 1
    46  	EVMOutputGas uint64 = (common.AddressLength + wrappers.LongLen + hashing.HashLen) * TxBytesGas
    47  	EVMInputGas  uint64 = (common.AddressLength+wrappers.LongLen+hashing.HashLen+wrappers.LongLen)*TxBytesGas + secp256k1fx.CostPerSignature
    48  )
    49  
    50  // EVMOutput defines an output that is added to the EVM state created by import transactions
    51  type EVMOutput struct {
    52  	Address common.Address `serialize:"true" json:"address"`
    53  	Amount  uint64         `serialize:"true" json:"amount"`
    54  	AssetID ids.ID         `serialize:"true" json:"assetID"`
    55  }
    56  
    57  // EVMInput defines an input created from the EVM state to fund export transactions
    58  type EVMInput struct {
    59  	Address common.Address `serialize:"true" json:"address"`
    60  	Amount  uint64         `serialize:"true" json:"amount"`
    61  	AssetID ids.ID         `serialize:"true" json:"assetID"`
    62  	Nonce   uint64         `serialize:"true" json:"nonce"`
    63  }
    64  
    65  // Verify ...
    66  func (out *EVMOutput) Verify() error {
    67  	switch {
    68  	case out == nil:
    69  		return errNilOutput
    70  	case out.Amount == 0:
    71  		return errNoValueOutput
    72  	case out.AssetID == ids.Empty:
    73  		return errEmptyAssetID
    74  	}
    75  	return nil
    76  }
    77  
    78  // Verify ...
    79  func (in *EVMInput) Verify() error {
    80  	switch {
    81  	case in == nil:
    82  		return errNilInput
    83  	case in.Amount == 0:
    84  		return errNoValueInput
    85  	case in.AssetID == ids.Empty:
    86  		return errEmptyAssetID
    87  	}
    88  	return nil
    89  }
    90  
    91  // UnsignedTx is an unsigned transaction
    92  type UnsignedTx interface {
    93  	Initialize(unsignedBytes, signedBytes []byte)
    94  	ID() ids.ID
    95  	GasUsed(fixedFee bool) (uint64, error)
    96  	Burned(assetID ids.ID) (uint64, error)
    97  	Bytes() []byte
    98  	SignedBytes() []byte
    99  }
   100  
   101  // UnsignedAtomicTx is an unsigned operation that can be atomically accepted
   102  type UnsignedAtomicTx interface {
   103  	UnsignedTx
   104  
   105  	// InputUTXOs returns the UTXOs this tx consumes
   106  	InputUTXOs() ids.Set
   107  	// Verify attempts to verify that the transaction is well formed
   108  	Verify(ctx *snow.Context, rules params.Rules) error
   109  	// Attempts to verify this transaction with the provided state.
   110  	SemanticVerify(vm *VM, stx *Tx, parent *Block, baseFee *big.Int, rules params.Rules) error
   111  	// AtomicOps returns the blockchainID and set of atomic requests that
   112  	// must be applied to shared memory for this transaction to be accepted.
   113  	// The set of atomic requests must be returned in a consistent order.
   114  	AtomicOps() (ids.ID, *atomic.Requests, error)
   115  
   116  	EVMStateTransfer(ctx *snow.Context, state *state.StateDB) error
   117  }
   118  
   119  // Tx is a signed transaction
   120  type Tx struct {
   121  	// The body of this transaction
   122  	UnsignedAtomicTx `serialize:"true" json:"unsignedTx"`
   123  
   124  	// The credentials of this transaction
   125  	Creds []verify.Verifiable `serialize:"true" json:"credentials"`
   126  }
   127  
   128  // Sign this transaction with the provided signers
   129  func (tx *Tx) Sign(c codec.Manager, signers [][]*crypto.PrivateKeySECP256K1R) error {
   130  	unsignedBytes, err := c.Marshal(codecVersion, &tx.UnsignedAtomicTx)
   131  	if err != nil {
   132  		return fmt.Errorf("couldn't marshal UnsignedAtomicTx: %w", err)
   133  	}
   134  
   135  	// Attach credentials
   136  	hash := hashing.ComputeHash256(unsignedBytes)
   137  	for _, keys := range signers {
   138  		cred := &secp256k1fx.Credential{
   139  			Sigs: make([][crypto.SECP256K1RSigLen]byte, len(keys)),
   140  		}
   141  		for i, key := range keys {
   142  			sig, err := key.SignHash(hash) // Sign hash
   143  			if err != nil {
   144  				return fmt.Errorf("problem generating credential: %w", err)
   145  			}
   146  			copy(cred.Sigs[i][:], sig)
   147  		}
   148  		tx.Creds = append(tx.Creds, cred) // Attach credential
   149  	}
   150  
   151  	signedBytes, err := c.Marshal(codecVersion, tx)
   152  	if err != nil {
   153  		return fmt.Errorf("couldn't marshal Tx: %w", err)
   154  	}
   155  	tx.Initialize(unsignedBytes, signedBytes)
   156  	return nil
   157  }
   158  
   159  // BlockFeeContribution calculates how much AVAX towards the block fee contribution was paid
   160  // for via this transaction denominated in [avaxAssetID] with [baseFee] used to calculate the
   161  // cost of this transaction. This function also returns the [gasUsed] by the
   162  // transaction for inclusion in the [baseFee] algorithm.
   163  func (tx *Tx) BlockFeeContribution(fixedFee bool, avaxAssetID ids.ID, baseFee *big.Int) (*big.Int, *big.Int, error) {
   164  	if baseFee == nil {
   165  		return nil, nil, errNilBaseFee
   166  	}
   167  	if baseFee.Cmp(common.Big0) <= 0 {
   168  		return nil, nil, fmt.Errorf("cannot calculate tip with base fee %d <= 0", baseFee)
   169  	}
   170  	gasUsed, err := tx.GasUsed(fixedFee)
   171  	if err != nil {
   172  		return nil, nil, err
   173  	}
   174  	txFee, err := calculateDynamicFee(gasUsed, baseFee)
   175  	if err != nil {
   176  		return nil, nil, err
   177  	}
   178  	burned, err := tx.Burned(avaxAssetID)
   179  	if err != nil {
   180  		return nil, nil, err
   181  	}
   182  	if txFee > burned {
   183  		return nil, nil, fmt.Errorf("insufficient AVAX burned (%d) to cover import tx fee (%d)", burned, txFee)
   184  	}
   185  	excessBurned := burned - txFee
   186  
   187  	// Calculate the amount of AVAX that has been burned above the required fee denominated
   188  	// in C-Chain native 18 decimal places
   189  	blockFeeContribution := new(big.Int).Mul(new(big.Int).SetUint64(excessBurned), x2cRate)
   190  	return blockFeeContribution, new(big.Int).SetUint64(gasUsed), nil
   191  }
   192  
   193  // innerSortInputsAndSigners implements sort.Interface for EVMInput
   194  type innerSortInputsAndSigners struct {
   195  	inputs  []EVMInput
   196  	signers [][]*crypto.PrivateKeySECP256K1R
   197  }
   198  
   199  func (ins *innerSortInputsAndSigners) Less(i, j int) bool {
   200  	addrComp := bytes.Compare(ins.inputs[i].Address.Bytes(), ins.inputs[j].Address.Bytes())
   201  	if addrComp != 0 {
   202  		return addrComp < 0
   203  	}
   204  	return bytes.Compare(ins.inputs[i].AssetID[:], ins.inputs[j].AssetID[:]) < 0
   205  }
   206  
   207  func (ins *innerSortInputsAndSigners) Len() int { return len(ins.inputs) }
   208  
   209  func (ins *innerSortInputsAndSigners) Swap(i, j int) {
   210  	ins.inputs[j], ins.inputs[i] = ins.inputs[i], ins.inputs[j]
   211  	ins.signers[j], ins.signers[i] = ins.signers[i], ins.signers[j]
   212  }
   213  
   214  // SortEVMInputsAndSigners sorts the list of EVMInputs based on the addresses and assetIDs
   215  func SortEVMInputsAndSigners(inputs []EVMInput, signers [][]*crypto.PrivateKeySECP256K1R) {
   216  	sort.Sort(&innerSortInputsAndSigners{inputs: inputs, signers: signers})
   217  }
   218  
   219  // IsSortedAndUniqueEVMInputs returns true if the EVM Inputs are sorted and unique
   220  // based on the account addresses
   221  func IsSortedAndUniqueEVMInputs(inputs []EVMInput) bool {
   222  	return utils.IsSortedAndUnique(&innerSortInputsAndSigners{inputs: inputs})
   223  }
   224  
   225  // innerSortEVMOutputs implements sort.Interface for EVMOutput
   226  type innerSortEVMOutputs struct {
   227  	outputs []EVMOutput
   228  }
   229  
   230  func (outs *innerSortEVMOutputs) Less(i, j int) bool {
   231  	addrComp := bytes.Compare(outs.outputs[i].Address.Bytes(), outs.outputs[j].Address.Bytes())
   232  	if addrComp != 0 {
   233  		return addrComp < 0
   234  	}
   235  	return bytes.Compare(outs.outputs[i].AssetID[:], outs.outputs[j].AssetID[:]) < 0
   236  }
   237  
   238  func (outs *innerSortEVMOutputs) Len() int { return len(outs.outputs) }
   239  
   240  func (outs *innerSortEVMOutputs) Swap(i, j int) {
   241  	outs.outputs[j], outs.outputs[i] = outs.outputs[i], outs.outputs[j]
   242  }
   243  
   244  // SortEVMOutputs sorts the list of EVMOutputs based on the addresses and assetIDs
   245  // of the outputs
   246  func SortEVMOutputs(outputs []EVMOutput) {
   247  	sort.Sort(&innerSortEVMOutputs{outputs: outputs})
   248  }
   249  
   250  // IsSortedEVMOutputs returns true if the EVMOutputs are sorted
   251  // based on the account addresses and assetIDs
   252  func IsSortedEVMOutputs(outputs []EVMOutput) bool {
   253  	return sort.IsSorted(&innerSortEVMOutputs{outputs: outputs})
   254  }
   255  
   256  // IsSortedAndUniqueEVMOutputs returns true if the EVMOutputs are sorted
   257  // and unique based on the account addresses and assetIDs
   258  func IsSortedAndUniqueEVMOutputs(outputs []EVMOutput) bool {
   259  	return utils.IsSortedAndUnique(&innerSortEVMOutputs{outputs: outputs})
   260  }
   261  
   262  // calculates the amount of AVAX that must be burned by an atomic transaction
   263  // that consumes [cost] at [baseFee].
   264  func calculateDynamicFee(cost uint64, baseFee *big.Int) (uint64, error) {
   265  	if baseFee == nil {
   266  		return 0, errNilBaseFee
   267  	}
   268  	bigCost := new(big.Int).SetUint64(cost)
   269  	fee := new(big.Int).Mul(bigCost, baseFee)
   270  	feeToRoundUp := new(big.Int).Add(fee, x2cRateMinus1)
   271  	feeInNAVAX := new(big.Int).Div(feeToRoundUp, x2cRate)
   272  	if !feeInNAVAX.IsUint64() {
   273  		// the fee is more than can fit in a uint64
   274  		return 0, errFeeOverflow
   275  	}
   276  	return feeInNAVAX.Uint64(), nil
   277  }
   278  
   279  func calcBytesCost(len int) uint64 {
   280  	return uint64(len) * TxBytesGas
   281  }
   282  
   283  // mergeAtomicOps merges atomic requests represented by [txs]
   284  // to the [output] map, depending on whether [chainID] is present in the map.
   285  func mergeAtomicOps(txs []*Tx) (map[ids.ID]*atomic.Requests, error) {
   286  	if len(txs) > 1 {
   287  		// txs should be stored in order of txID to ensure consistency
   288  		// with txs initialized from the txID index.
   289  		copyTxs := make([]*Tx, len(txs))
   290  		copy(copyTxs, txs)
   291  		sort.Slice(copyTxs, func(i, j int) bool { return copyTxs[i].ID().Hex() < copyTxs[j].ID().Hex() })
   292  		txs = copyTxs
   293  	}
   294  	output := make(map[ids.ID]*atomic.Requests)
   295  	for _, tx := range txs {
   296  		chainID, txRequests, err := tx.UnsignedAtomicTx.AtomicOps()
   297  		if err != nil {
   298  			return nil, err
   299  		}
   300  		mergeAtomicOpsToMap(output, chainID, txRequests)
   301  	}
   302  	return output, nil
   303  }
   304  
   305  // mergeAtomicOps merges atomic ops for [chainID] represented by [requests]
   306  // to the [output] map provided.
   307  func mergeAtomicOpsToMap(output map[ids.ID]*atomic.Requests, chainID ids.ID, requests *atomic.Requests) {
   308  	if request, exists := output[chainID]; exists {
   309  		request.PutRequests = append(request.PutRequests, requests.PutRequests...)
   310  		request.RemoveRequests = append(request.RemoveRequests, requests.RemoveRequests...)
   311  	} else {
   312  		output[chainID] = requests
   313  	}
   314  }