github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/execution/contexts/call_context.go (about)

     1  package contexts
     2  
     3  import (
     4  	"fmt"
     5  	"math/big"
     6  
     7  	"github.com/hyperledger/burrow/execution/vms"
     8  
     9  	"github.com/hyperledger/burrow/acm"
    10  	"github.com/hyperledger/burrow/acm/acmstate"
    11  	"github.com/hyperledger/burrow/crypto"
    12  	"github.com/hyperledger/burrow/execution/engine"
    13  	"github.com/hyperledger/burrow/execution/errors"
    14  	"github.com/hyperledger/burrow/execution/exec"
    15  	"github.com/hyperledger/burrow/logging"
    16  	"github.com/hyperledger/burrow/logging/structure"
    17  	"github.com/hyperledger/burrow/txs/payload"
    18  )
    19  
    20  // TODO: make configurable
    21  const GasLimit = uint64(1000000)
    22  
    23  type CallContext struct {
    24  	VMS           *vms.VirtualMachines
    25  	State         acmstate.ReaderWriter
    26  	MetadataState acmstate.MetadataReaderWriter
    27  	Blockchain    engine.Blockchain
    28  	RunCall       bool
    29  	Logger        *logging.Logger
    30  	tx            *payload.CallTx
    31  	txe           *exec.TxExecution
    32  }
    33  
    34  func (ctx *CallContext) Execute(txe *exec.TxExecution, p payload.Payload) error {
    35  	var ok bool
    36  	ctx.tx, ok = p.(*payload.CallTx)
    37  	if !ok {
    38  		return fmt.Errorf("payload must be CallTx, but is: %v", p)
    39  	}
    40  	ctx.txe = txe
    41  	inAcc, outAcc, err := ctx.Precheck()
    42  	if err != nil {
    43  		return err
    44  	}
    45  	// That the fee less than the input amount is checked by Precheck to be greater than or equal to fee
    46  	value := ctx.tx.Input.Amount - ctx.tx.Fee
    47  
    48  	if ctx.RunCall {
    49  		return ctx.Deliver(inAcc, outAcc, value)
    50  	}
    51  	return ctx.Check(inAcc, value)
    52  }
    53  
    54  func (ctx *CallContext) Precheck() (*acm.Account, *acm.Account, error) {
    55  	var outAcc *acm.Account
    56  	// Validate input
    57  	inAcc, err := ctx.State.GetAccount(ctx.tx.Input.Address)
    58  	if err != nil {
    59  		return nil, nil, err
    60  	}
    61  	if inAcc == nil {
    62  		return nil, nil, errors.Errorf(errors.Codes.InvalidAddress,
    63  			"Cannot find input account: %v", ctx.tx.Input)
    64  	}
    65  
    66  	if ctx.tx.Input.Amount < ctx.tx.Fee {
    67  		return nil, nil, errors.Errorf(errors.Codes.InsufficientFunds,
    68  			"Send did not send enough to cover the fee: %v", ctx.tx.Input)
    69  	}
    70  
    71  	// Fees are handle by the CallContext, values transfers (i.e. balances) are handled in the VM (or in Check())
    72  	err = inAcc.SubtractFromBalance(ctx.tx.Fee)
    73  	if err != nil {
    74  		return nil, nil, errors.Errorf(errors.Codes.InsufficientFunds,
    75  			"Input account %v (balance: %d) does not have sufficient balance to cover input amount: %v",
    76  			inAcc.Address, inAcc.Balance, ctx.tx.Input)
    77  	}
    78  
    79  	// Calling a nil destination is defined as requesting contract creation
    80  	createContract := ctx.tx.Address == nil
    81  
    82  	if createContract {
    83  		if !hasCreateContractPermission(ctx.State, inAcc, ctx.Logger) {
    84  			return nil, nil, fmt.Errorf("account %s does not have CreateContract permission", ctx.tx.Input.Address)
    85  		}
    86  	} else {
    87  		if !hasCallPermission(ctx.State, inAcc, ctx.Logger) {
    88  			return nil, nil, fmt.Errorf("account %s does not have Call permission", ctx.tx.Input.Address)
    89  		}
    90  
    91  		// Output account may be nil if we are still in mempool and contract was created in same block as this tx
    92  		// but that's fine, because the account will be created properly when the create tx runs in the block
    93  		// and then this won't return nil. otherwise, we take their fee
    94  		// Note: ctx.tx.Address == nil iff createContract so dereference is okay
    95  		outAcc, err = ctx.State.GetAccount(*ctx.tx.Address)
    96  		if err != nil {
    97  			return nil, nil, err
    98  		}
    99  	}
   100  
   101  	err = ctx.State.UpdateAccount(inAcc)
   102  	if err != nil {
   103  		return nil, nil, err
   104  	}
   105  	return inAcc, outAcc, nil
   106  }
   107  
   108  func (ctx *CallContext) Check(inAcc *acm.Account, value uint64) error {
   109  	// We do a trial balance subtraction here
   110  	err := inAcc.SubtractFromBalance(value)
   111  	if err != nil {
   112  		return err
   113  	}
   114  	err = ctx.State.UpdateAccount(inAcc)
   115  	if err != nil {
   116  		return err
   117  	}
   118  	return nil
   119  }
   120  
   121  func (ctx *CallContext) Deliver(inAcc, outAcc *acm.Account, value uint64) error {
   122  	// VM call variables
   123  	createContract := ctx.tx.Address == nil
   124  	caller := inAcc.Address
   125  	txCache := acmstate.NewCache(ctx.State, acmstate.Named("TxCache"))
   126  	metaCache := acmstate.NewMetadataCache(ctx.MetadataState)
   127  
   128  	var callee crypto.Address
   129  	var code []byte
   130  	var wcode []byte
   131  
   132  	// get or create callee
   133  	if createContract {
   134  		// We already checked for permission
   135  		callee = crypto.NewContractAddress(caller, ctx.txe.TxHash)
   136  		code = ctx.tx.Data
   137  		wcode = ctx.tx.WASM
   138  		err := engine.CreateAccount(txCache, callee)
   139  		if err != nil {
   140  			return err
   141  		}
   142  		ctx.Logger.TraceMsg("Creating new contract",
   143  			"contract_address", callee,
   144  			"init_code", code)
   145  
   146  		// store abis
   147  		err = engine.UpdateContractMeta(txCache, metaCache, callee, ctx.tx.ContractMeta)
   148  		if err != nil {
   149  			return err
   150  		}
   151  	} else {
   152  		if outAcc == nil {
   153  			// if you call an account that doesn't exist
   154  			// or an account with no code then we take fees (sorry pal)
   155  			// NOTE: it's fine to create a contract and call it within one
   156  			// block (sequence number will prevent re-ordering of those txs)
   157  			// but to create with one contract and call with another
   158  			// you have to wait a block to avoid a re-ordering attack
   159  			// that will take your fees
   160  			exception := errors.Errorf(errors.Codes.InvalidAddress,
   161  				"CallTx to an address (%v) that does not exist", ctx.tx.Address)
   162  			ctx.Logger.Info.Log(structure.ErrorKey, exception,
   163  				"caller_address", inAcc.GetAddress(),
   164  				"callee_address", ctx.tx.Address)
   165  			ctx.txe.PushError(exception)
   166  			ctx.CallEvents(exception)
   167  			return nil
   168  		}
   169  		callee = outAcc.Address
   170  		acc, err := txCache.GetAccount(callee)
   171  		if err != nil {
   172  			return err
   173  		}
   174  		code = acc.EVMCode
   175  		wcode = acc.WASMCode
   176  		ctx.Logger.TraceMsg("Calling existing contract",
   177  			"contract_address", callee,
   178  			"input", ctx.tx.Data,
   179  			"evm_code", code,
   180  			"wasm_code", wcode)
   181  	}
   182  	ctx.Logger.Trace.Log("callee", callee)
   183  
   184  	var ret []byte
   185  	var err error
   186  	txHash := ctx.txe.Envelope.Tx.Hash()
   187  	gas := new(big.Int).SetUint64(ctx.tx.GasLimit)
   188  
   189  	params := engine.CallParams{
   190  		Origin: caller,
   191  		Caller: caller,
   192  		Callee: callee,
   193  		Input:  ctx.tx.Data,
   194  		Value:  *new(big.Int).SetUint64(value),
   195  		Gas:    gas,
   196  	}
   197  
   198  	if len(wcode) != 0 {
   199  		// TODO: accept options
   200  		ret, err = ctx.VMS.WVM.Execute(txCache, ctx.Blockchain, ctx.txe, params, wcode)
   201  		if err != nil {
   202  			// Failure. Charge the gas fee. The 'value' was otherwise not transferred.
   203  			ctx.Logger.InfoMsg("Error on WASM execution",
   204  				structure.ErrorKey, err)
   205  
   206  			ctx.txe.PushError(errors.Wrap(err, "call error"))
   207  		} else {
   208  			ctx.Logger.TraceMsg("Successful execution")
   209  			if createContract {
   210  				err := engine.InitWASMCode(txCache, callee, ret)
   211  				if err != nil {
   212  					return err
   213  				}
   214  			}
   215  			err = ctx.Sync(txCache, metaCache)
   216  			if err != nil {
   217  				return err
   218  			}
   219  		}
   220  	} else {
   221  		// EVM
   222  		ctx.VMS.EVM.SetNonce(txHash)
   223  		ctx.VMS.EVM.SetLogger(ctx.Logger.With(structure.TxHashKey, txHash))
   224  
   225  		ret, err = ctx.VMS.EVM.Execute(txCache, ctx.Blockchain, ctx.txe, params, code)
   226  
   227  		if err != nil {
   228  			// Failure. Charge the gas fee. The 'value' was otherwise not transferred.
   229  			ctx.Logger.InfoMsg("Error on EVM execution",
   230  				structure.ErrorKey, err)
   231  
   232  			ctx.txe.PushError(err)
   233  		} else {
   234  			ctx.Logger.TraceMsg("Successful execution")
   235  			if createContract {
   236  				err := engine.InitEVMCode(txCache, callee, ret)
   237  				if err != nil {
   238  					return err
   239  				}
   240  			}
   241  			err = ctx.Sync(txCache, metaCache)
   242  			if err != nil {
   243  				return err
   244  			}
   245  		}
   246  		ctx.CallEvents(err)
   247  	}
   248  	// Gas starts life as a uint64 and should only been reduced (used up) over a transaction so .Uint64() is safe
   249  	ctx.txe.Return(ret, ctx.tx.GasLimit-gas.Uint64())
   250  	// Create a receipt from the ret and whether it erred.
   251  	ctx.Logger.TraceMsg("VM Call complete",
   252  		"caller", caller,
   253  		"callee", callee,
   254  		"return", ret,
   255  		structure.ErrorKey, err)
   256  
   257  	return nil
   258  }
   259  
   260  func (ctx *CallContext) CallEvents(err error) {
   261  	// Fire Events for sender and receiver a separate event will be fired from vm for each additional call
   262  	ctx.txe.Input(ctx.tx.Input.Address, errors.AsException(err))
   263  	if ctx.tx.Address != nil {
   264  		ctx.txe.Input(*ctx.tx.Address, errors.AsException(err))
   265  	}
   266  }
   267  
   268  func (ctx *CallContext) Sync(cache *acmstate.Cache, metaCache *acmstate.MetadataCache) error {
   269  	err := cache.Sync(ctx.State)
   270  	if err != nil {
   271  		return err
   272  	}
   273  	return metaCache.Sync(ctx.MetadataState)
   274  }