def Contract_Create(self, engine): script = engine.EvaluationStack.Pop().GetByteArray() if len(script) > 1024 * 1024: return False param_list = engine.EvaluationStack.Pop().GetByteArray() if len(param_list) > 252: return False return_type = int(engine.EvaluationStack.Pop().GetBigInteger()) contract_properties = int(engine.EvaluationStack.Pop().GetBigInteger()) if len(engine.EvaluationStack.Peek().GetByteArray()) > 252: return False name = engine.EvaluationStack.Pop().GetByteArray() if len(engine.EvaluationStack.Peek().GetByteArray()) > 252: return False code_version = engine.EvaluationStack.Pop().GetByteArray() if len(engine.EvaluationStack.Peek().GetByteArray()) > 252: return False author = engine.EvaluationStack.Pop().GetByteArray() if len(engine.EvaluationStack.Peek().GetByteArray()) > 252: return False email = engine.EvaluationStack.Pop().GetByteArray() if len(engine.EvaluationStack.Peek().GetByteArray()) > 65536: return False description = engine.EvaluationStack.Pop().GetByteArray() hash = Crypto.ToScriptHash(script, unhex=False) contract = self._contracts.TryGet(hash.ToBytes()) if contract is None: code = FunctionCode(script=script, param_list=param_list, return_type=return_type, contract_properties=contract_properties) contract = ContractState(code, contract_properties, name, code_version, author, email, description) self._contracts.GetAndChange(code.ScriptHash().ToBytes(), contract) self._contracts_created[hash.ToBytes()] = UInt160( data=engine.CurrentContext.ScriptHash()) engine.EvaluationStack.PushT(StackItem.FromInterface(contract)) self.events_to_dispatch.append( SmartContractEvent(SmartContractEvent.CONTRACT_CREATED, [contract], hash, Blockchain.Default().Height, engine.ScriptContainer.Hash if engine.ScriptContainer else None, test_mode=engine.testMode)) return True
class SmartContractEvent(SerializableMixin): """ SmartContractEvent is sent as argument to all smart contract event handlers. It includes all the information about the current event, such as type, payload, contract hash, transaction hash, and block number. - event_type (str) - contract_hash (UInt160) - tx_hash (UInt256) - block_number (int) - event_payload (object[]) - execution_success (bool) - test_mode (bool) `event_payload` is always a list of object, depending on what data types you sent in the smart contract. """ RUNTIME_NOTIFY = "SmartContract.Runtime.Notify" # payload: object[] RUNTIME_LOG = "SmartContract.Runtime.Log" # payload: bytes EXECUTION = "SmartContract.Execution.*" EXECUTION_INVOKE = "SmartContract.Execution.Invoke" EXECUTION_SUCCESS = "SmartContract.Execution.Success" EXECUTION_FAIL = "SmartContract.Execution.Fail" VERIFICATION = "SmartContract.Verification.*" VERIFICATION_SUCCESS = "SmartContract.Verification.Success" VERIFICATION_FAIL = "SmartContract.Verification.Fail" STORAGE = "SmartContract.Storage.*" STORAGE_GET = "SmartContract.Storage.Get" STORAGE_PUT = "SmartContract.Storage.Put" STORAGE_DELETE = "SmartContract.Storage.Delete" CONTRACT = "SmartContract.Contract.*" CONTRACT_CREATED = "SmartContract.Contract.Create" CONTRACT_MIGRATED = "SmartContract.Contract.Migrate" CONTRACT_DESTROY = "SmartContract.Contract.Destroy" event_type = None event_payload = None contract_hash = None block_number = None tx_hash = None execution_success = None test_mode = None contract = None token = None def __init__(self, event_type, event_payload, contract_hash, block_number, tx_hash, execution_success=False, test_mode=False): self.event_type = event_type self.event_payload = event_payload self.contract_hash = contract_hash self.block_number = block_number self.tx_hash = tx_hash self.execution_success = execution_success self.test_mode = test_mode self.token = None if not self.event_payload: self.event_payload = [] if self.event_type in [ SmartContractEvent.CONTRACT_CREATED, SmartContractEvent.CONTRACT_MIGRATED ]: if len(self.event_payload) and isinstance(self.event_payload[0], ContractState): self.contract = self.event_payload[0] def Serialize(self, writer): writer.WriteVarString(self.event_type.encode('utf-8')) writer.WriteUInt160(self.contract_hash) writer.WriteUInt32(self.block_number) writer.WriteUInt256(self.tx_hash) self.SerializePayload(writer) def SerializePayload(self, writer): if self.event_type in [ SmartContractEvent.CONTRACT_CREATED, SmartContractEvent.CONTRACT_MIGRATED ] and self.contract: self.contract.Serialize(writer) if self.token: self.token.Serialize(writer) def Deserialize(self, reader): self.event_type = reader.ReadVarString().decode('utf-8') self.contract_hash = reader.ReadUInt160() self.block_number = reader.ReadUInt32() self.tx_hash = reader.ReadUInt256() self.DeserializePayload(reader) def DeserializePayload(self, reader): if self.event_type in [ SmartContractEvent.CONTRACT_CREATED, SmartContractEvent.CONTRACT_MIGRATED ]: self.contract = ContractState() self.contract.Deserialize(reader) try: from neo.Wallets.NEP5Token import NEP5Token token = NEP5Token(binascii.hexlify(self.contract.Code.Script)) token.Deserialize(reader) self.token = token except Exception as e: logger.error("Couldnt deserialize token %s " % e) def __str__(self): return "SmartContractEvent(event_type=%s, event_payload=%s, contract_hash=%s, block_number=%s, tx_hash=%s, execution_success=%s, test_mode=%s)" \ % (self.event_type, self.event_payload, self.contract_hash, self.block_number, self.tx_hash, self.execution_success, self.test_mode) def ToByteArray(self): stream = StreamManager.GetStream() writer = BinaryWriter(stream) self.Serialize(writer) out = stream.getvalue() StreamManager.ReleaseStream(stream) return out @staticmethod def FromByteArray(data): stream = StreamManager.GetStream(data=data) reader = BinaryReader(stream) etype = reader.ReadVarString().decode('utf-8') reader.stream.seek(0) if etype == SmartContractEvent.RUNTIME_NOTIFY: event = NotifyEvent(None, None, None, None, None) else: event = SmartContractEvent(None, None, None, None, None) event.Deserialize(reader) StreamManager.ReleaseStream(stream) return event def CheckIsNEP5(self): if self.contract and self.contract.IsNEP5Contract: self.token = self.contract._nep_token def ToJson(self): jsn = { 'type': self.event_type, 'contract': self.contract_hash.To0xString(), 'block': self.block_number, 'tx': self.tx_hash.To0xString() } if self.event_type in [ SmartContractEvent.CONTRACT_CREATED, SmartContractEvent.CONTRACT_MIGRATED ]: jsn['contract'] = self.contract.ToJson() if self.token: jsn['token'] = self.token.ToJson() return jsn
def Persist(self, block): sn = self._db.snapshot() accounts = DBCollection(self._db, sn, DBPrefix.ST_Account, AccountState) unspentcoins = DBCollection(self._db, sn, DBPrefix.ST_Coin, UnspentCoinState) spentcoins = DBCollection(self._db, sn, DBPrefix.ST_SpentCoin, SpentCoinState) assets = DBCollection(self._db, sn, DBPrefix.ST_Asset, AssetState) validators = DBCollection(self._db, sn, DBPrefix.ST_Validator, ValidatorState) contracts = DBCollection(self._db, sn, DBPrefix.ST_Contract, ContractState) storages = DBCollection(self._db, sn, DBPrefix.ST_Storage, StorageItem) amount_sysfee = self.GetSysFeeAmount( block.PrevHash) + block.TotalFees().value amount_sysfee_bytes = amount_sysfee.to_bytes(8, 'little') with self._db.write_batch() as wb: for tx in block.Transactions: unspentcoinstate = UnspentCoinState.FromTXOutputsConfirmed( tx.outputs) unspentcoins.Add(tx.Hash.ToBytes(), unspentcoinstate) # go through all the accounts in the tx outputs for output in tx.outputs: account = accounts.GetAndChange( output.AddressBytes, AccountState(output.ScriptHash)) if account.HasBalance(output.AssetId): account.AddToBalance(output.AssetId, output.Value) else: account.SetBalanceFor(output.AssetId, output.Value) # go through all tx inputs unique_tx_input_hashes = [] for input in tx.inputs: if input.PrevHash not in unique_tx_input_hashes: unique_tx_input_hashes.append(input.PrevHash) for txhash in unique_tx_input_hashes: prevTx, height = self.GetTransaction(txhash.ToBytes()) coin_refs_by_hash = [ coinref for coinref in tx.inputs if coinref.PrevHash.ToBytes() == txhash.ToBytes() ] for input in coin_refs_by_hash: uns = unspentcoins.GetAndChange( input.PrevHash.ToBytes()) uns.OrEqValueForItemAt(input.PrevIndex, CoinState.Spent) if prevTx.outputs[input.PrevIndex].AssetId.ToBytes( ) == Blockchain.SystemShare().Hash.ToBytes(): sc = spentcoins.GetAndChange( input.PrevHash.ToBytes(), SpentCoinState(input.PrevHash, height, [])) sc.Items.append( SpentCoinItem(input.PrevIndex, block.Index)) output = prevTx.outputs[input.PrevIndex] acct = accounts.GetAndChange( prevTx.outputs[input.PrevIndex].AddressBytes, AccountState(output.ScriptHash)) assetid = prevTx.outputs[input.PrevIndex].AssetId acct.SubtractFromBalance( assetid, prevTx.outputs[input.PrevIndex].Value) # do a whole lotta stuff with tx here... if tx.Type == TransactionType.RegisterTransaction: asset = AssetState(tx.Hash, tx.AssetType, tx.Name, tx.Amount, Fixed8(0), tx.Precision, Fixed8(0), Fixed8(0), UInt160(data=bytearray(20)), tx.Owner, tx.Admin, tx.Admin, block.Index + 2 * 2000000, False) assets.Add(tx.Hash.ToBytes(), asset) elif tx.Type == TransactionType.IssueTransaction: txresults = [ result for result in tx.GetTransactionResults() if result.Amount.value < 0 ] for result in txresults: asset = assets.GetAndChange(result.AssetId.ToBytes()) asset.Available = asset.Available - result.Amount elif tx.Type == TransactionType.ClaimTransaction: for input in tx.Claims: sc = spentcoins.TryGet(input.PrevHash.ToBytes()) if sc and sc.HasIndex(input.PrevIndex): sc.DeleteIndex(input.PrevIndex) spentcoins.GetAndChange(input.PrevHash.ToBytes()) elif tx.Type == TransactionType.EnrollmentTransaction: validator = validators.GetAndChange( tx.PublicKey, ValidatorState(pub_key=tx.PublicKey)) # logger.info("VALIDATOR %s " % validator.ToJson()) elif tx.Type == TransactionType.StateTransaction: # @TODO Implement persistence for State Descriptors pass elif tx.Type == TransactionType.PublishTransaction: contract = ContractState(tx.Code, tx.NeedStorage, tx.Name, tx.CodeVersion, tx.Author, tx.Email, tx.Description) contracts.GetAndChange(tx.Code.ScriptHash().ToBytes(), contract) elif tx.Type == TransactionType.InvocationTransaction: script_table = CachedScriptTable(contracts) service = StateMachine(accounts, validators, assets, contracts, storages, wb=wb) engine = ApplicationEngine( trigger_type=TriggerType.Application, container=tx, table=script_table, service=service, gas=tx.Gas, testMode=True) engine.LoadScript(tx.Script, False) # normally, this function does not return true/false # for testing purposes, we try to execute and if an exception is raised # we will return false, otherwise if success return true # this is different than the 'success' bool returned by engine.Execute() # the 'success' bool returned by engine.Execute() is a value indicating # wether or not the invocation was successful, and if so, we then commit # the changes made by the contract to the database try: success = engine.Execute() # service.ExecutionCompleted(engine, success) return True except Exception as e: # service.ExecutionCompleted(self, False, e) return False
def Contract_Migrate(self, engine): script = engine.EvaluationStack.Pop().GetByteArray() if len(script) > 1024 * 1024: return False param_list = engine.EvaluationStack.Pop().GetByteArray() if len(param_list) > 252: return False return_type = int(engine.EvaluationStack.Pop().GetBigInteger()) contract_properties = engine.EvaluationStack.Pop().GetBigInteger() if len(engine.EvaluationStack.Peek().GetByteArray()) > 252: return False name = engine.EvaluationStack.Pop().GetByteArray().decode('utf-8') if len(engine.EvaluationStack.Peek().GetByteArray()) > 252: return False version = engine.EvaluationStack.Pop().GetByteArray().decode('utf-8') if len(engine.EvaluationStack.Peek().GetByteArray()) > 252: return False author = engine.EvaluationStack.Pop().GetByteArray().decode('utf-8') if len(engine.EvaluationStack.Peek().GetByteArray()) > 252: return False email = engine.EvaluationStack.Pop().GetByteArray().decode('utf-8') if len(engine.EvaluationStack.Peek().GetByteArray()) > 65536: return False description = engine.EvaluationStack.Pop().GetByteArray().decode( 'utf-8') hash = Crypto.ToScriptHash(script, unhex=False) contract = self._contracts.TryGet(hash.ToBytes()) if contract is None: code = FunctionCode(script=script, param_list=param_list, return_type=return_type) contract = ContractState(code=code, contract_properties=contract_properties, name=name, version=version, author=author, email=email, description=description) self._contracts.Add(hash.ToBytes(), contract) self._contracts_created[hash.ToBytes()] = UInt160( data=engine.CurrentContext.ScriptHash()) if contract.HasStorage: for key, val in self._storages.Find( engine.CurrentContext.ScriptHash()).items(): key = StorageKey(script_hash=hash, key=key) item = StorageItem(val) self._storages.Add(key.ToArray(), item) engine.EvaluationStack.PushT(StackItem.FromInterface(contract)) self.events_to_dispatch.append( SmartContractEvent(SmartContractEvent.CONTRACT_MIGRATED, ContractParameter( ContractParameterType.InteropInterface, contract), hash, Blockchain.Default().Height + 1, engine.ScriptContainer.Hash if engine.ScriptContainer else None, test_mode=engine.testMode)) return self.Contract_Destroy(engine)
def Persist(self, block): self._persisting_block = block accounts = DBCollection(self._db, DBPrefix.ST_Account, AccountState) unspentcoins = DBCollection(self._db, DBPrefix.ST_Coin, UnspentCoinState) spentcoins = DBCollection(self._db, DBPrefix.ST_SpentCoin, SpentCoinState) assets = DBCollection(self._db, DBPrefix.ST_Asset, AssetState) validators = DBCollection(self._db, DBPrefix.ST_Validator, ValidatorState) contracts = DBCollection(self._db, DBPrefix.ST_Contract, ContractState) storages = DBCollection(self._db, DBPrefix.ST_Storage, StorageItem) amount_sysfee = self.GetSysFeeAmount( block.PrevHash) + block.TotalFees().value amount_sysfee_bytes = amount_sysfee.to_bytes(8, 'little') to_dispatch = [] with self._db.write_batch() as wb: wb.put(DBPrefix.DATA_Block + block.Hash.ToBytes(), amount_sysfee_bytes + block.Trim()) for tx in block.Transactions: wb.put(DBPrefix.DATA_Transaction + tx.Hash.ToBytes(), block.IndexBytes() + tx.ToArray()) # go through all outputs and add unspent coins to them unspentcoinstate = UnspentCoinState.FromTXOutputsConfirmed( tx.outputs) unspentcoins.Add(tx.Hash.ToBytes(), unspentcoinstate) # go through all the accounts in the tx outputs for output in tx.outputs: account = accounts.GetAndChange( output.AddressBytes, AccountState(output.ScriptHash)) if account.HasBalance(output.AssetId): account.AddToBalance(output.AssetId, output.Value) else: account.SetBalanceFor(output.AssetId, output.Value) # go through all tx inputs unique_tx_input_hashes = [] for input in tx.inputs: if input.PrevHash not in unique_tx_input_hashes: unique_tx_input_hashes.append(input.PrevHash) for txhash in unique_tx_input_hashes: prevTx, height = self.GetTransaction(txhash.ToBytes()) coin_refs_by_hash = [ coinref for coinref in tx.inputs if coinref.PrevHash.ToBytes() == txhash.ToBytes() ] for input in coin_refs_by_hash: uns = unspentcoins.GetAndChange( input.PrevHash.ToBytes()) uns.OrEqValueForItemAt(input.PrevIndex, CoinState.Spent) if prevTx.outputs[input.PrevIndex].AssetId.ToBytes( ) == Blockchain.SystemShare().Hash.ToBytes(): sc = spentcoins.GetAndChange( input.PrevHash.ToBytes(), SpentCoinState(input.PrevHash, height, [])) sc.Items.append( SpentCoinItem(input.PrevIndex, block.Index)) output = prevTx.outputs[input.PrevIndex] acct = accounts.GetAndChange( prevTx.outputs[input.PrevIndex].AddressBytes, AccountState(output.ScriptHash)) assetid = prevTx.outputs[input.PrevIndex].AssetId acct.SubtractFromBalance( assetid, prevTx.outputs[input.PrevIndex].Value) # do a whole lotta stuff with tx here... if tx.Type == TransactionType.RegisterTransaction: asset = AssetState(tx.Hash, tx.AssetType, tx.Name, tx.Amount, Fixed8(0), tx.Precision, Fixed8(0), Fixed8(0), UInt160(data=bytearray(20)), tx.Owner, tx.Admin, tx.Admin, block.Index + 2 * 2000000, False) assets.Add(tx.Hash.ToBytes(), asset) elif tx.Type == TransactionType.IssueTransaction: txresults = [ result for result in tx.GetTransactionResults() if result.Amount.value < 0 ] for result in txresults: asset = assets.GetAndChange(result.AssetId.ToBytes()) asset.Available = asset.Available - result.Amount elif tx.Type == TransactionType.ClaimTransaction: for input in tx.Claims: sc = spentcoins.TryGet(input.PrevHash.ToBytes()) if sc and sc.HasIndex(input.PrevIndex): sc.DeleteIndex(input.PrevIndex) spentcoins.GetAndChange(input.PrevHash.ToBytes()) elif tx.Type == TransactionType.EnrollmentTransaction: newvalidator = ValidatorState(pub_key=tx.PublicKey) validators.GetAndChange(tx.PublicKey.ToBytes(), newvalidator) elif tx.Type == TransactionType.StateTransaction: # @TODO Implement persistence for State Descriptors pass elif tx.Type == TransactionType.PublishTransaction: contract = ContractState(tx.Code, tx.NeedStorage, tx.Name, tx.CodeVersion, tx.Author, tx.Email, tx.Description) contracts.GetAndChange(tx.Code.ScriptHash().ToBytes(), contract) elif tx.Type == TransactionType.InvocationTransaction: script_table = CachedScriptTable(contracts) service = StateMachine(accounts, validators, assets, contracts, storages, wb) engine = ApplicationEngine( trigger_type=TriggerType.Application, container=tx, table=script_table, service=service, gas=tx.Gas, testMode=False) engine.LoadScript(tx.Script) try: success = engine.Execute() service.ExecutionCompleted(engine, success) except Exception as e: service.ExecutionCompleted(engine, False, e) to_dispatch = to_dispatch + service.events_to_dispatch else: if tx.Type != b'\x00' and tx.Type != 128: logger.info("TX Not Found %s " % tx.Type) # do save all the accounts, unspent, coins, validators, assets, etc # now sawe the current sys block # filter out accounts to delete then commit for key, account in accounts.Current.items(): if not account.IsFrozen and len( account.Votes) == 0 and account.AllBalancesZeroOrLess( ): accounts.Remove(key) accounts.Commit(wb) # filte out unspent coins to delete then commit for key, unspent in unspentcoins.Current.items(): if unspent.IsAllSpent: unspentcoins.Remove(key) unspentcoins.Commit(wb) # filter out spent coins to delete then commit to db for key, spent in spentcoins.Current.items(): if len(spent.Items) == 0: spentcoins.Remove(key) spentcoins.Commit(wb) # commit validators validators.Commit(wb) # commit assets assets.Commit(wb) # commit contracts contracts.Commit(wb) wb.put(DBPrefix.SYS_CurrentBlock, block.Hash.ToBytes() + block.IndexBytes()) self._current_block_height = block.Index self._persisting_block = None self.TXProcessed += len(block.Transactions) for event in to_dispatch: events.emit(event.event_type, event)