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 }