|
26 | 26 | from skale.utils.web3_utils import init_web3 |
27 | 27 | from skale.wallets import Web3Wallet |
28 | 28 |
|
29 | | -from src.configs import Config, get_config |
| 29 | +from src.configs import Config, Source, get_config |
30 | 30 | from src.state import State, StateManager |
31 | 31 |
|
32 | 32 | logger = logging.getLogger(__name__) |
33 | 33 |
|
| 34 | +SOURCE_ID_SHIFT = 248 |
| 35 | + |
| 36 | + |
| 37 | +def extract_source_id(payment_id: int) -> int: |
| 38 | + return payment_id >> SOURCE_ID_SHIFT |
| 39 | + |
| 40 | + |
| 41 | +def resolve_source_id(mainnet_cs: MainnetCreditStation) -> int: |
| 42 | + last_payment_id = mainnet_cs.credit_station.contract.functions.getLastPaymentId().call() |
| 43 | + return extract_source_id(last_payment_id) |
| 44 | + |
34 | 45 |
|
35 | 46 | def run_distributor() -> None: |
36 | 47 | config = get_config() |
37 | 48 | state_manager = StateManager(state_file=config.general.state_file) |
38 | | - state = state_manager.load(config.general.from_block) |
| 49 | + state = state_manager.load({s.name: s.from_block for s in config.sources}) |
39 | 50 |
|
40 | | - schain_web3 = init_web3(config.endpoints.schain) |
| 51 | + schain_web3 = init_web3(config.destination.endpoint) |
41 | 52 | schain_wallet = Web3Wallet(config.general.eth_private_key, schain_web3) |
42 | | - mainnet_cs = MainnetCreditStation(config.endpoints.mainnet, config.contracts.mainnet) |
43 | | - schain_cs = SchainCreditStation(config.endpoints.schain, config.contracts.schain, schain_wallet) |
| 53 | + schain_cs = SchainCreditStation( |
| 54 | + config.destination.endpoint, config.destination.contract, schain_wallet |
| 55 | + ) |
| 56 | + source_clients = { |
| 57 | + source.name: MainnetCreditStation(source.endpoint, source.contract) |
| 58 | + for source in config.sources |
| 59 | + } |
| 60 | + |
| 61 | + for source in config.sources: |
| 62 | + source.source_id = resolve_source_id(source_clients[source.name]) |
| 63 | + logger.info(f'[{source.name}] Resolved on-chain source_id={source.source_id}') |
| 64 | + |
44 | 65 | while True: |
45 | | - try: |
46 | | - logging.info('Starting credit distribution cycle') |
47 | | - state = distribute_credits(mainnet_cs, schain_cs, config, state) |
48 | | - state_manager.save(state) |
49 | | - logger.info(f'Sleeping for {config.agent.loop_sleep} seconds before next cycle') |
50 | | - sleep(config.agent.loop_sleep) |
51 | | - except Exception as e: |
52 | | - logging.exception(f'Error during credit distribution cycle: {e}') |
53 | | - logger.info(f'Sleeping for {config.agent.exception_sleep} seconds before retrying') |
54 | | - sleep(config.agent.exception_sleep) |
55 | | - |
56 | | - |
57 | | -def distribute_credits( |
| 66 | + logger.info('Starting credit distribution cycle') |
| 67 | + for source in config.sources: |
| 68 | + try: |
| 69 | + state = distribute_credits_for_source( |
| 70 | + source, source_clients[source.name], schain_cs, config, state |
| 71 | + ) |
| 72 | + state_manager.save(state) |
| 73 | + except Exception as e: |
| 74 | + logger.exception( |
| 75 | + f'Error processing source {source.name!r}: {e}; continuing with next' |
| 76 | + ) |
| 77 | + logger.info(f'Sleeping for {config.agent.loop_sleep} seconds before next cycle') |
| 78 | + sleep(config.agent.loop_sleep) |
| 79 | + |
| 80 | + |
| 81 | +def distribute_credits_for_source( |
| 82 | + source: Source, |
58 | 83 | mainnet_cs: MainnetCreditStation, |
59 | 84 | schain_cs: SchainCreditStation, |
60 | 85 | config: Config, |
61 | 86 | state: State, |
62 | 87 | ) -> State: |
| 88 | + from_block = state.from_blocks[source.name] |
| 89 | + logger.info(f'[{source.name}] Fetching events from block {from_block}') |
63 | 90 | all_events = mainnet_cs.credit_station.get_payment_received_events( |
64 | | - from_block=state.from_block, schain_name=config.general.schain_name |
| 91 | + from_block=from_block, schain_name=config.general.schain_name |
65 | 92 | ) |
66 | | - last_block = state.from_block |
67 | 93 | for event in all_events: |
68 | | - fulfill_payment(event, schain_cs, config) |
| 94 | + fulfill_payment(source, event, schain_cs) |
69 | 95 |
|
70 | | - last_block_with_event = 0 |
71 | | - if len(all_events) != 0: |
72 | | - last_block_with_event = all_events[-1]['block_number'] |
| 96 | + if all_events: |
| 97 | + state.from_blocks[source.name] = all_events[-1]['block_number'] + 1 |
73 | 98 | else: |
74 | | - logger.info('No new PaymentReceived events found.') |
75 | | - state.from_block = max(last_block, last_block_with_event + 1) |
| 99 | + logger.info(f'[{source.name}] No new PaymentReceived events found.') |
76 | 100 | return state |
77 | 101 |
|
78 | 102 |
|
79 | | -def get_payment_wei_value(config: Config) -> int: |
80 | | - return config.payment.value_eth * 10**18 |
81 | | - |
82 | | - |
83 | 103 | def fulfill_payment( |
84 | | - event: PaymentReceivedEvent, schain_cs: SchainCreditStation, config: Config |
| 104 | + source: Source, |
| 105 | + event: PaymentReceivedEvent, |
| 106 | + schain_cs: SchainCreditStation, |
85 | 107 | ) -> None: |
86 | | - logger.info(f'Checking payment: {event["payment_id"]}') |
87 | | - is_fulfilled = schain_cs.ledger.is_fulfilled(event['payment_id']) |
| 108 | + payment_id = event['payment_id'] |
| 109 | + logger.info(f'[{source.name}] Checking payment: {payment_id}') |
| 110 | + is_fulfilled = schain_cs.ledger.is_fulfilled(payment_id) |
88 | 111 | if not is_fulfilled: |
89 | | - logger.info(f'Fulfilling payment: {event["payment_id"]}') |
90 | | - schain_cs.ledger.fulfill( |
91 | | - event['payment_id'], event['to_address'], value=get_payment_wei_value(config) |
92 | | - ) |
93 | | - logger.info(f'Payment {event["payment_id"]} fulfilled successfully.') |
| 112 | + logger.info(f'[{source.name}] Fulfilling payment: {payment_id}') |
| 113 | + schain_cs.ledger.fulfill(payment_id, event['to_address'], value=event['value']) |
| 114 | + logger.info(f'[{source.name}] Payment {payment_id} fulfilled successfully.') |
94 | 115 | else: |
95 | | - logger.debug(f'Payment {event["payment_id"]} is already fulfilled.') |
| 116 | + logger.debug(f'[{source.name}] Payment {payment_id} is already fulfilled.') |
96 | 117 |
|
97 | 118 |
|
98 | 119 | if __name__ == '__main__': |
|
0 commit comments