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 }