-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdaemon.py
More file actions
178 lines (157 loc) · 9.72 KB
/
Copy pathdaemon.py
File metadata and controls
178 lines (157 loc) · 9.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
from asyncio import sleep, new_event_loop
from copy import deepcopy
from decimal import Decimal
from os import environ
from denaro import Database
from denaro.constants import SMALLEST
from denaro.helpers import sha256, point_to_string
from denaro.transactions import CoinbaseTransaction, Transaction
from dvm.contract import ContractCallList, ContractCall, ContractCreation, ContractsCache, Address, Block, CONTRACT_METHOD_TIMEOUT
from dvm.vm import DVM, contract_globals
from dvm.serializer import serialize
from dvm.timeout import timeout
from dvm.contract import DVMTransaction
Database.credentials = {
'user': environ.get('DENARO_DATABASE_USER', 'denaro'),
'password': environ.get('DENARO_DATABASE_PASSWORD', ''),
'database': environ.get('DENARO_DATABASE_NAME', 'denaro')
}
# it will change before the stable release
DVM_ADDRESS = 'DsmArTjpJNuEBuHB2x4f14cDifdduTtu2CR1BMs1P5RcF'
async def main():
denaro_database: Database = await Database.get()
dvm = DVM(denaro_database)
contract_globals['get_block'] = dvm.get_block
async with denaro_database.pool.acquire() as connection:
res = await connection.fetchrow('SELECT block_no FROM dvm_state ORDER BY block_no DESC LIMIT 1')
if res:
i = res['block_no'] + 1
#i = 23966 - 1
#i = await denaro_database.get_next_block_id()
while True:
block = await denaro_database.get_block_by_id(i)
if block is not None:
block_hash = block['hash']
i += 1
# transactions with only one input can be filtered by the query.
# a kind of multisig could be implemented by making able to use more input addresses and provide a list of them to the smart contract
async with denaro_database.pool.acquire() as connection:
txs = await connection.fetch('SELECT tx_hex FROM transactions WHERE block_hash = $1 AND $2 = ANY(outputs_addresses)', block_hash, DVM_ADDRESS)
txs = [await Transaction.from_hex(tx['tx_hex'], False) for tx in txs]
if len(txs) == 0:
continue
print(i)
calls = []
for tx in txs:
if isinstance(tx, CoinbaseTransaction):
continue
if len(set([point_to_string(await tx_input.get_public_key()) for tx_input in tx.inputs])) != 1:
print('Skipping transaction because too many input addresses')
continue
if any(output.address == DVM_ADDRESS for output in tx.outputs):
payload = tx.message
try:
contract_call_list = ContractCallList.from_payload(payload)
except Exception as e:
print('Invalid payload:', e)
continue
await tx.get_fees()
dvm_tx = DVMTransaction(tx.hash(), tx.outputs)
for index, output in enumerate(tx.outputs):
if output.address == DVM_ADDRESS:
# fixme rename
# fixme change way it is created
contract_creation_hash = sha256(bytes.fromhex(block_hash) + bytes.fromhex(tx.hash()) + bytes([index]))
calls.append({
'contract_call': contract_call_list.contract_calls[index],
'tx_hash': tx.hash(),
'dvm_tx': dvm_tx,
'output_index': index,
# fixme show only if deploying a contract
'contract_creation_hash': contract_creation_hash,
'sender': point_to_string(await tx.inputs[0].get_public_key()),
'fees': output.amount,
'fee_rate': (tx.fees / len(tx.hex()) / 2) if tx.fees > 0 else (1 / Decimal(SMALLEST))
})
if not calls:
continue
contracts_hashes = [contract_call.contract_hash for contract_call in [call['contract_call'] for call in calls] if contract_call.__class__ == ContractCall]
ContractsCache.contracts = await dvm.get_contracts(contracts_hashes)
ContractsCache.current_block = Block(block)
dvm_transactions = []
emitted_events = []
for call in calls:
contract_call, tx_hash, output_index, sender = call['contract_call'], call['tx_hash'], call['output_index'], call['sender']
ContractsCache.contracts_backup = deepcopy(ContractsCache.contracts)
state_backup = ContractsCache.contracts_backup
ContractsCache.current_transaction = call['dvm_tx']
ContractsCache.additional_gas = 0
ContractsCache.emitted_events = []
ContractsCache.created_contracts = []
if isinstance(contract_call, ContractCreation):
if contract := await dvm.create_contract(contract_call, call['contract_creation_hash'], tx_hash, block['id'], sender, contract_call.args):
ContractsCache.additional_gas = len(contract_call.source_code)
else:
continue
else:
contract = ContractsCache.contracts[contract_call.contract_hash]
ContractsCache.current_contract_hash = contract_call.contract_hash
ContractsCache.contract_instances = [contract_call.contract_hash]
if contract_call.contract_hash not in ContractsCache.contracts:
print(f'Skipping call because contract {contract_call.contract_hash} does not exist')
continue
if contract_call.method == 'constructor': # fixme
print('Cannot call constructor')
continue
if contract_call.method not in contract._methods:
print(f'Skipping call because contract {contract_call.contract_hash} does not have {contract_call.method} method')
continue
try:
await timeout(CONTRACT_METHOD_TIMEOUT, contract._methods[contract_call.method], Address(sender), *contract_call.args)
except (Exception, KeyboardInterrupt) as e:
ContractsCache.contracts = state_backup
print(f'Transaction in contract {contract_call.contract_hash} reverted because of {e.__class__.__name__}: {str(e)}')
raise
continue
# todo fix implementation
"""if ContractsCache.created_contracts:
for i, (creator_contract_hash, class_name, specifier, args, kwargs) in enumerate(ContractsCache.created_contracts):
source_code = (await dvm.get_contracts_source(creator_contract_hash))[creator_contract_hash]
source_code += f'Contract.deployed = {class_name}'
contract_creation = ContractCreation(specifier, source_code, ())
contract_creation_hash = sha256(bytes.fromhex(call['contract_creation_hash']) + bytes([i]))
if contract := await dvm.create_contract(contract_creation, contract_creation_hash, tx_hash, block['id'], creator_contract_hash, args, kwargs):
ContractsCache.contracts[contract_creation_hash] = contract"""
state_size_delta = abs(
len(serialize(
{contract.address: contract._variables for
contract in state_backup.values()})) -
len(serialize(
{contract.address: contract._variables for
contract in ContractsCache.contracts.values()}))
)
print(state_size_delta, 'bytes for state change')
if ContractsCache.emitted_events:
state_size_delta += len(serialize([event.to_dict() for _, event in ContractsCache.emitted_events]))
print(len(serialize([event.to_dict() for _, event in ContractsCache.emitted_events])), 'bytes for events')
print(ContractsCache.additional_gas, 'additional_gas')
total_gas = state_size_delta + len(ContractsCache.contract_instances) * 1024 + ContractsCache.additional_gas
print(total_gas, 'total gas')
fees_required = total_gas * call['fee_rate']
if call['fees'] < fees_required:
ContractsCache.contracts = state_backup
print(total_gas, call['fee_rate'])
print(f'Transaction in contract {contract_call.contract_hash} reverted because of not enough gas: sent {call["fees"]} while required {fees_required}')
continue
if ContractsCache.emitted_events:
emitted_events.extend((tx_hash, output_index, contract_hash, *event.to_tuple()) for contract_hash, event in ContractsCache.emitted_events)
print(fees_required, contract._variables)
dvm_transactions.append((contract._contract_hash, tx_hash, output_index, contract_call.get_payload().hex()))
updated_contract_states = {contract.address: contract.get_json_state() for contract in ContractsCache.contracts.values()}
await dvm.update_contract_states(updated_contract_states, block['id'])
await dvm.add_transactions(dvm_transactions)
await dvm.add_events(emitted_events)
else:
await sleep(3)
if __name__ == '__main__':
new_event_loop().run_until_complete(main())