github.com/datachainlab/burrow@v0.25.0/execution/contexts/call_context.go (about)

     1  package contexts
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/hyperledger/burrow/crypto"
     7  
     8  	"github.com/hyperledger/burrow/acm"
     9  	"github.com/hyperledger/burrow/acm/acmstate"
    10  	"github.com/hyperledger/burrow/execution/errors"
    11  	"github.com/hyperledger/burrow/execution/evm"
    12  	"github.com/hyperledger/burrow/execution/exec"
    13  	"github.com/hyperledger/burrow/logging"
    14  	"github.com/hyperledger/burrow/logging/structure"
    15  	"github.com/hyperledger/burrow/txs/payload"
    16  )
    17  
    18  // TODO: make configurable
    19  const GasLimit = uint64(1000000)
    20  
    21  type CallContext struct {
    22  	StateWriter acmstate.ReaderWriter
    23  	RunCall     bool
    24  	Blockchain  Blockchain
    25  	VMOptions   []func(*evm.VM)
    26  	Logger      *logging.Logger
    27  	tx          *payload.CallTx
    28  	txe         *exec.TxExecution
    29  }
    30  
    31  func (ctx *CallContext) Execute(txe *exec.TxExecution, p payload.Payload) error {
    32  	var ok bool
    33  	ctx.tx, ok = p.(*payload.CallTx)
    34  	if !ok {
    35  		return fmt.Errorf("payload must be CallTx, but is: %v", p)
    36  	}
    37  	ctx.txe = txe
    38  	inAcc, outAcc, err := ctx.Precheck()
    39  	if err != nil {
    40  		return err
    41  	}
    42  	// That the fee less than the input amount is checked by Precheck to be greater than or equal to fee
    43  	value := ctx.tx.Input.Amount - ctx.tx.Fee
    44  
    45  	if ctx.RunCall {
    46  		return ctx.Deliver(inAcc, outAcc, value)
    47  	}
    48  	return ctx.Check(inAcc, value)
    49  }
    50  
    51  func (ctx *CallContext) Precheck() (*acm.Account, *acm.Account, error) {
    52  	var outAcc *acm.Account
    53  	// Validate input
    54  	inAcc, err := ctx.StateWriter.GetAccount(ctx.tx.Input.Address)
    55  	if err != nil {
    56  		return nil, nil, err
    57  	}
    58  	if inAcc == nil {
    59  		return nil, nil, errors.ErrorCodef(errors.ErrorCodeInvalidAddress,
    60  			"Cannot find input account: %v", ctx.tx.Input)
    61  	}
    62  
    63  	if ctx.tx.Input.Amount < ctx.tx.Fee {
    64  		return nil, nil, errors.ErrorCodef(errors.ErrorCodeInsufficientFunds,
    65  			"Send did not send enough to cover the fee: %v", ctx.tx.Input)
    66  	}
    67  
    68  	// Fees are handle by the CallContext, values transfers (i.e. balances) are handled in the VM (or in Check())
    69  	err = inAcc.SubtractFromBalance(ctx.tx.Fee)
    70  	if err != nil {
    71  		return nil, nil, errors.ErrorCodef(errors.ErrorCodeInsufficientFunds,
    72  			"Input account does not have sufficient balance to cover input amount: %v", ctx.tx.Input)
    73  	}
    74  
    75  	// Calling a nil destination is defined as requesting contract creation
    76  	createContract := ctx.tx.Address == nil
    77  
    78  	if createContract {
    79  		if !hasCreateContractPermission(ctx.StateWriter, inAcc, ctx.Logger) {
    80  			return nil, nil, fmt.Errorf("account %s does not have CreateContract permission", ctx.tx.Input.Address)
    81  		}
    82  	} else {
    83  		if !hasCallPermission(ctx.StateWriter, inAcc, ctx.Logger) {
    84  			return nil, nil, fmt.Errorf("account %s does not have Call permission", ctx.tx.Input.Address)
    85  		}
    86  		// check if its a native contract
    87  		if evm.IsRegisteredNativeContract(*ctx.tx.Address) {
    88  			return nil, nil, errors.ErrorCodef(errors.ErrorCodeReservedAddress,
    89  				"attempt to call a native contract at %s, "+
    90  					"but native contracts cannot be called using CallTx. Use a "+
    91  					"contract that calls the native contract or the appropriate tx "+
    92  					"type (eg. PermsTx, NameTx)", ctx.tx.Address)
    93  		}
    94  
    95  		// Output account may be nil if we are still in mempool and contract was created in same block as this tx
    96  		// but that's fine, because the account will be created properly when the create tx runs in the block
    97  		// and then this won't return nil. otherwise, we take their fee
    98  		// Note: ctx.tx.Address == nil iff createContract so dereference is okay
    99  		outAcc, err = ctx.StateWriter.GetAccount(*ctx.tx.Address)
   100  		if err != nil {
   101  			return nil, nil, err
   102  		}
   103  	}
   104  
   105  	err = ctx.StateWriter.UpdateAccount(inAcc)
   106  	if err != nil {
   107  		return nil, nil, err
   108  	}
   109  	return inAcc, outAcc, nil
   110  }
   111  
   112  func (ctx *CallContext) Check(inAcc *acm.Account, value uint64) error {
   113  	// We do a trial balance subtraction here
   114  	err := inAcc.SubtractFromBalance(value)
   115  	if err != nil {
   116  		return err
   117  	}
   118  	err = ctx.StateWriter.UpdateAccount(inAcc)
   119  	if err != nil {
   120  		return err
   121  	}
   122  	return nil
   123  }
   124  
   125  func (ctx *CallContext) Deliver(inAcc, outAcc *acm.Account, value uint64) error {
   126  	createContract := ctx.tx.Address == nil
   127  	// VM call variables
   128  	var (
   129  		gas     uint64         = ctx.tx.GasLimit
   130  		caller  crypto.Address = inAcc.Address
   131  		callee  crypto.Address = crypto.ZeroAddress // initialized below
   132  		code    []byte         = nil
   133  		ret     []byte         = nil
   134  		txCache                = evm.NewState(ctx.StateWriter, ctx.Blockchain.BlockHash, acmstate.Named("TxCache"))
   135  		params                 = evm.Params{
   136  			BlockHeight: ctx.Blockchain.LastBlockHeight() + 1,
   137  			BlockTime:   ctx.Blockchain.LastBlockTime().Unix(),
   138  			GasLimit:    GasLimit,
   139  		}
   140  	)
   141  
   142  	// get or create callee
   143  	if createContract {
   144  		// We already checked for permission
   145  		callee = crypto.NewContractAddress(caller, ctx.txe.TxHash)
   146  		code = ctx.tx.Data
   147  		txCache.CreateAccount(callee)
   148  		ctx.Logger.TraceMsg("Creating new contract",
   149  			"contract_address", callee,
   150  			"init_code", code)
   151  	} else {
   152  		if outAcc == nil || len(outAcc.Code) == 0 {
   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  			var exception *errors.Exception
   161  			if outAcc == nil {
   162  				exception = errors.ErrorCodef(errors.ErrorCodeInvalidAddress,
   163  					"CallTx to an address (%v) that does not exist", ctx.tx.Address)
   164  				ctx.Logger.Info.Log(structure.ErrorKey, exception,
   165  					"caller_address", inAcc.GetAddress(),
   166  					"callee_address", ctx.tx.Address)
   167  			} else {
   168  				exception = errors.ErrorCodef(errors.ErrorCodeInvalidAddress,
   169  					"CallTx to an address (%v) that holds no code", ctx.tx.Address)
   170  				ctx.Logger.Info.Log(exception,
   171  					"caller_address", inAcc.GetAddress(),
   172  					"callee_address", ctx.tx.Address)
   173  			}
   174  			ctx.txe.PushError(exception)
   175  			ctx.CallEvents(exception)
   176  			return nil
   177  		}
   178  		callee = outAcc.Address
   179  		code = txCache.GetCode(callee)
   180  		ctx.Logger.TraceMsg("Calling existing contract",
   181  			"contract_address", callee,
   182  			"input", ctx.tx.Data,
   183  			"contract_code", code)
   184  	}
   185  	ctx.Logger.Trace.Log("callee", callee)
   186  
   187  	txHash := ctx.txe.Envelope.Tx.Hash()
   188  	logger := ctx.Logger.With(structure.TxHashKey, txHash)
   189  	vmach := evm.NewVM(params, caller, txHash, logger, ctx.VMOptions...)
   190  	ret, exception := vmach.Call(txCache, ctx.txe, caller, callee, code, ctx.tx.Data, value, &gas)
   191  	if exception != nil {
   192  		// Failure. Charge the gas fee. The 'value' was otherwise not transferred.
   193  		ctx.Logger.InfoMsg("Error on execution",
   194  			structure.ErrorKey, exception)
   195  
   196  		ctx.txe.PushError(errors.ErrorCodef(exception.ErrorCode(), "call error: %s\nEVM call trace: %s",
   197  			exception.String(), ctx.txe.CallTrace()))
   198  	} else {
   199  		ctx.Logger.TraceMsg("Successful execution")
   200  		if createContract {
   201  			txCache.InitCode(callee, ret)
   202  		}
   203  		err := txCache.Sync()
   204  		if err != nil {
   205  			return err
   206  		}
   207  	}
   208  	ctx.CallEvents(exception)
   209  	ctx.txe.Return(ret, ctx.tx.GasLimit-gas)
   210  	// Create a receipt from the ret and whether it erred.
   211  	ctx.Logger.TraceMsg("VM call complete",
   212  		"caller", caller,
   213  		"callee", callee,
   214  		"return", ret,
   215  		structure.ErrorKey, exception)
   216  	return nil
   217  }
   218  
   219  func (ctx *CallContext) CallEvents(err error) {
   220  	// Fire Events for sender and receiver a separate event will be fired from vm for each additional call
   221  	ctx.txe.Input(ctx.tx.Input.Address, errors.AsException(err))
   222  	if ctx.tx.Address != nil {
   223  		ctx.txe.Input(*ctx.tx.Address, errors.AsException(err))
   224  	}
   225  }