diff --git a/.tool-versions b/.tool-versions index a50ce8eb..ea59406b 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,10 +1,10 @@ -circleci 0.1.31425 -golang 1.24.5 +circleci 0.1.31425 +golang 1.26.3 golangci-lint 1.64.5 -nodejs 18.17.0 -postgres 14.2 -pre-commit 4.1.0 -protoc 29.3 -shfmt 3.10.0 -solidity 0.8.9 -sqlc 1.28.0 +nodejs 18.17.0 +postgres 18.3 +pre-commit 4.1.0 +protoc 29.3 +shfmt 3.10.0 +solidity 0.8.9 +sqlc latest diff --git a/mise-test-setup/mise-tasks/gen-keyper-configs b/mise-test-setup/mise-tasks/gen-keyper-configs index 27961297..a9f91a0f 100755 --- a/mise-test-setup/mise-tasks/gen-keyper-configs +++ b/mise-test-setup/mise-tasks/gen-keyper-configs @@ -67,6 +67,9 @@ for index in range(int(os.environ["NUM_KEYPERS"])): elif deployment_type == "service": utils.set_toml_path(document, ["Chain", "Node", "EthereumURL"], "ws://ethereum:8545") utils.set_toml_path(document, ["Chain", "Contracts", "ShutterRegistry"], utils.get_created_contract_address(deployment_run, "ShutterRegistry")) + event_trigger_registry = utils.get_uups_proxy_address(deployment_run, "ShutterEventTriggerRegistryV1") + if event_trigger_registry: + utils.set_toml_path(document, ["Chain", "Contracts", "ShutterEventTriggerRegistry"], event_trigger_registry) else: raise SystemExit(f"Unsupported DEPLOYMENT_TYPE for config generation: {deployment_type}") diff --git a/mise-test-setup/mise-tasks/init-keyper-dbs b/mise-test-setup/mise-tasks/init-keyper-dbs index c38453a0..df45896f 100755 --- a/mise-test-setup/mise-tasks/init-keyper-dbs +++ b/mise-test-setup/mise-tasks/init-keyper-dbs @@ -3,6 +3,7 @@ #MISE depends=["up-db", "gen-keyper-configs"] import os +from pathlib import Path import utils @@ -47,39 +48,101 @@ def public_table_count(database_name: str) -> int: return int(result.stdout.strip() or "0") +def db_ethereum_address(database_name: str) -> str: + result = utils.run( + [ + "docker", + "compose", + "exec", + "-T", + "db", + "psql", + "-U", + "postgres", + "-d", + database_name, + "-tAc", + "SELECT value FROM meta_inf WHERE key = 'ethereum address'", + ], + capture_output=True, + ) + return result.stdout.strip() + + +def drop_db(database_name: str) -> None: + utils.run( + [ + "docker", + "compose", + "exec", + "-T", + "db", + "psql", + "-U", + "postgres", + "-c", + f"DROP DATABASE IF EXISTS \"{database_name}\"", + ] + ) + + +def create_db(database_name: str) -> None: + utils.run( + [ + "docker", + "compose", + "exec", + "-T", + "db", + "createdb", + "-U", + "postgres", + database_name, + ] + ) + + +def init_db(index: int) -> None: + utils.run( + [ + "docker", + "compose", + "run", + "-T", + "--no-deps", + "--rm", + f"keyper-{index}", + utils.KEYPER_SUBCOMMANDS[deployment_type], + "initdb", + "--config", + "/config.toml", + ] + ) + + deployment_type = utils.resolve_deployment_type(os.environ.get("DEPLOYMENT_TYPE", "")) +data_dir = Path(os.environ["DATA_DIR"]) for index in range(int(os.environ["NUM_KEYPERS"])): database_name = f"keyper-{index}" + config_path = data_dir / f"keyper-{index}.toml" if not db_exists(database_name): - utils.run( - [ - "docker", - "compose", - "exec", - "-T", - "db", - "createdb", - "-U", - "postgres", - database_name, - ] - ) + create_db(database_name) + init_db(index) + continue if public_table_count(database_name) == 0: - utils.run( - [ - "docker", - "compose", - "run", - "-T", - "--no-deps", - "--rm", - f"keyper-{index}", - utils.KEYPER_SUBCOMMANDS[deployment_type], - "initdb", - "--config", - "/config.toml", - ] + init_db(index) + continue + + # DB exists with tables. Re-initialize if the stored address doesn't match the config. + config_addr = utils.keyper_address(config_path) + db_addr = db_ethereum_address(database_name) + if db_addr.lower() != config_addr.lower(): + print( + f"keyper-{index}: DB address {db_addr} != config address {config_addr}; re-initializing" ) + drop_db(database_name) + create_db(database_name) + init_db(index) diff --git a/mise-test-setup/mise-tasks/test-event-decryption b/mise-test-setup/mise-tasks/test-event-decryption new file mode 100755 index 00000000..e935a740 --- /dev/null +++ b/mise-test-setup/mise-tasks/test-event-decryption @@ -0,0 +1,356 @@ +#!/usr/bin/env -S uv run --script +# MISE description="E2E test: event-based decryption triggers without batching" +# MISE depends=["wait-for-initial-dkg", "deploy"] + +import json +import os +import secrets +import time +from pathlib import Path + +import utils + +# --------------------------------------------------------------------------- +# RLP helpers — minimal subset needed for EventTriggerDefinition encoding +# --------------------------------------------------------------------------- + + +def _rlp_bytes(data: bytes) -> bytes: + if len(data) == 1 and data[0] < 0x80: + return data + if len(data) == 0: + return b"\x80" + if len(data) <= 55: + return bytes([0x80 + len(data)]) + data + lb = len(data).to_bytes((len(data).bit_length() + 7) // 8, "big") + return bytes([0xB7 + len(lb)]) + lb + data + + +def _rlp_uint(n: int) -> bytes: + if n == 0: + return b"\x80" + raw = n.to_bytes((n.bit_length() + 7) // 8, "big") + return _rlp_bytes(raw) + + +def _rlp_list(items: list[bytes]) -> bytes: + payload = b"".join(items) + if len(payload) <= 55: + return bytes([0xC0 + len(payload)]) + payload + lb = len(payload).to_bytes((len(payload).bit_length() + 7) // 8, "big") + return bytes([0xF7 + len(lb)]) + lb + payload + + +def encode_trigger_def( + contract_addr: bytes, topic_index: int, topic_value: bytes +) -> bytes: + """ + Encode an EventTriggerDefinition (version 0x02 + RLP) that matches logs from + `contract_addr` where topic[topic_index] == topic_value (32 bytes). + + Mirrors Go's EventTriggerDefinition.MarshalBytes(). + + ValuePredicate uses a custom EncodeRLP that produces a flat list + [Op, *int_args, *byte_args] — NOT [Op, [IntArgs], [ByteArgs]]. + For BytesEq (Op=5) there are 0 int args and 1 byte arg, so the list is [5, topic_value]. + """ + log_value_ref = _rlp_list( + [ + _rlp_uint(0), # Dynamic = false (bool encoded as uint 0) + _rlp_uint(topic_index), + ] + ) + # Flat list: [Op, byteArg] (no IntArgs for BytesEq, one ByteArg) + value_predicate = _rlp_list( + [ + _rlp_uint(5), # Op = BytesEq + _rlp_bytes(topic_value), # the single byte argument, directly in the list + ] + ) + log_predicate = _rlp_list([log_value_ref, value_predicate]) + definition = _rlp_list( + [ + _rlp_bytes(contract_addr), + _rlp_list([log_predicate]), + ] + ) + return bytes([0x02]) + definition + + +# --------------------------------------------------------------------------- +# Contract deployment + interaction via cast inside the ethereum container +# --------------------------------------------------------------------------- + +RPC = "http://127.0.0.1:8545" + + +def cast(*args: str, capture: bool = True) -> str: + """Run a cast subcommand inside the running ethereum container.""" + result = utils.run( + ["docker", "compose", "exec", "-T", "ethereum", "cast", *args], + capture_output=capture, + ) + return result.stdout.strip() if capture else "" + + +def cast_send(*args: str) -> dict: + """Broadcast a transaction and return the parsed JSON receipt.""" + raw = cast( + "send", + "--json", + "--rpc-url", + RPC, + "--private-key", + os.environ["DEPLOY_KEY"], + *args, + ) + return json.loads(raw) + + +def deploy_contract(bytecode_hex: str) -> str: + """Deploy a contract from hex bytecode; returns the checksummed contract address.""" + receipt = cast_send("--create", bytecode_hex) + addr = receipt.get("contractAddress") or receipt.get("creates") + if not addr: + raise SystemExit( + f"Could not find contractAddress in receipt:\n{json.dumps(receipt, indent=2)}" + ) + return addr + + +def call_register( + registry_addr: str, + keyper_config_index: int, + identity_prefix_hex: str, + trigger_def_hex: str, + ttl: int, +) -> None: + cast_send( + registry_addr, + "register(uint64,bytes32,bytes,uint64)", + str(keyper_config_index), + f"0x{identity_prefix_hex}", + f"0x{trigger_def_hex}", + str(ttl), + ) + + +def call_trigger(helper_addr: str, topic2_hex: str) -> None: + zero32 = "0x" + "0" * 64 + cast_send( + helper_addr, + "trigger(uint64,bytes32,bytes32,bytes32)", + "0", + f"0x{topic2_hex}", + zero32, + zero32, + ) + + +def wait_for_event_decryption_key( + keyper_config_index: int, + identity_prefix_hex: str, + poll_interval: float = 1.0, + timeout: float = 120.0, +) -> str: + """ + Poll until the decryption key for the event trigger with the given identity_prefix + is available. Returns the hex-encoded identity (epoch_id). + """ + deadline = time.time() + timeout + while True: + if time.time() > deadline: + raise SystemExit( + f"Timeout waiting for decryption key for " + f"identity_prefix={identity_prefix_hex}, eon={keyper_config_index}" + ) + + identity = utils.query_keyper_db( + 0, + f"SELECT encode(identity, 'hex') FROM event_trigger_registered_event " + f"WHERE eon = {keyper_config_index} " + f"AND encode(identity_prefix, 'hex') = '{identity_prefix_hex}' " + "LIMIT 1", + ) + if not identity: + time.sleep(poll_interval) + continue + + exists = utils.query_keyper_db( + 0, + "SELECT EXISTS (" + " SELECT 1 FROM decryption_key " + f" WHERE eon = {keyper_config_index} " + f" AND encode(epoch_id, 'hex') = '{identity}'" + ")", + ) + if exists == "t": + print( + f" Decryption key ready: identity_prefix={identity_prefix_hex} → epoch_id={identity}" + ) + return identity + + time.sleep(poll_interval) + + +def assert_no_batching( + keyper_config_index: int, + num_triggers: int, + poll_interval: float = 1.0, + timeout: float = 30.0, +) -> None: + """ + Assert that decryption_signatures contains `num_triggers` distinct identities_hash + values for the given eon. With no batching each DecryptionTrigger carries exactly + one identity, so its identities_hash = keccak256(identity). With batching all N + identities would share one hash. + """ + deadline = time.time() + timeout + while True: + count_str = utils.query_keyper_db( + 0, + f"SELECT COUNT(DISTINCT identities_hash) FROM decryption_signatures " + f"WHERE eon = {keyper_config_index}", + ) + count = int(count_str) if count_str else 0 + if count >= num_triggers: + print( + f"No-batching assertion passed: " + f"{count} distinct identities_hash for {num_triggers} triggers" + ) + return + if time.time() > deadline: + raise SystemExit( + f"No-batching assertion failed: expected {num_triggers} distinct " + f"identities_hash in decryption_signatures for eon={keyper_config_index}, " + f"got {count}" + ) + time.sleep(poll_interval) + + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + + +def get_registry_address(data_dir: Path) -> str: + broadcast = json.loads( + ( + data_dir + / "contracts" + / "broadcast" + / utils.DEPLOYMENT_SCRIPTS["service"] + / os.environ["ETHEREUM_CHAIN_ID"] + / "run-latest.json" + ).read_text() + ) + addr = utils.get_uups_proxy_address(broadcast, "ShutterEventTriggerRegistryV1") + if not addr: + raise SystemExit( + "ShutterEventTriggerRegistryV1 proxy not found in deployment artifacts" + ) + return addr + + +def get_helper_bytecode(data_dir: Path) -> str: + artifact = json.loads( + ( + data_dir + / "contracts" + / "out" + / "EventTriggerTestHelper.sol" + / "EventTriggerTestHelper.json" + ).read_text() + ) + return artifact["bytecode"]["object"].removeprefix("0x") + + +def main() -> None: + deployment_type = utils.resolve_deployment_type( + os.environ.get("DEPLOYMENT_TYPE", "") + ) + if deployment_type != "service": + raise SystemExit("test-event-decryption only supports DEPLOYMENT_TYPE=service") + + data_dir = Path(os.environ["DATA_DIR"]) + poll_interval = float(os.environ.get("DECRYPTION_KEY_POLL_INTERVAL", "1")) + num_triggers = 3 + + # --- Resolve eon and keyper_config_index ----------------------------------- + # ShutterEventTriggerRegistryV1.register() takes the *keyper_config_index*, + # not the eon number. We obtain both from the eons table and use accordingly. + eon_str = utils.query_keyper_db(0, "SELECT max(eon) FROM eons") + if not eon_str: + raise SystemExit("No eon found in keyper DB — has DKG completed?") + eon_number = int(eon_str) + + keyper_config_index_str = utils.query_keyper_db( + 0, f"SELECT keyper_config_index FROM eons WHERE eon = {eon_number} LIMIT 1" + ) + if not keyper_config_index_str: + raise SystemExit(f"No keyper_config_index found for eon={eon_number}") + keyper_config_index = int(keyper_config_index_str) + + print(f"Using eon={eon_number}, keyper_config_index={keyper_config_index}") + + # --- Deploy EventTriggerTestHelper ---------------------------------------- + print("Deploying EventTriggerTestHelper…") + helper_bytecode = get_helper_bytecode(data_dir) + helper_addr = deploy_contract(helper_bytecode) + print(f" EventTriggerTestHelper deployed at {helper_addr}") + + # --- Build the trigger definition ----------------------------------------- + # Match Trigger(…, topic2=trigger_topic, …) events emitted by helper_addr. + # topic[2] is the second indexed parameter of the Trigger event (bytes32). + trigger_topic = secrets.token_bytes(32) + trigger_topic_hex = trigger_topic.hex() + + helper_addr_bytes = bytes.fromhex(helper_addr.removeprefix("0x").zfill(40)) + trigger_def = encode_trigger_def( + contract_addr=helper_addr_bytes, + topic_index=2, # topic[2] = topic2 (bytes32 indexed) + topic_value=trigger_topic, + ) + trigger_def_hex = trigger_def.hex() + print(f" EventTriggerDefinition: 0x{trigger_def_hex}") + + # --- Generate identity prefixes (one per trigger) ------------------------- + identity_prefixes = [secrets.token_bytes(32).hex() for _ in range(num_triggers)] + + # --- Register N event triggers -------------------------------------------- + registry_addr = get_registry_address(data_dir) + ttl = 10_000 # blocks until expiry + + print(f"Registering {num_triggers} event triggers on registry {registry_addr}…") + for ip_hex in identity_prefixes: + call_register(registry_addr, keyper_config_index, ip_hex, trigger_def_hex, ttl) + print(f" Registered trigger with identity_prefix=0x{ip_hex[:8]}…") + + # Give keypers time to sync the EventTriggerRegistered events before we fire. + # With ETHEREUM_BLOCK_TIME=1 a couple of seconds is sufficient. + sync_wait = max(3, int(os.environ.get("ACTIVATION_DELTA", "10"))) + print(f"Waiting {sync_wait}s for keypers to sync trigger registrations…") + time.sleep(sync_wait) + + # --- Fire one Trigger event — all 3 registered definitions will match ----- + print(f"Firing Trigger event with topic2=0x{trigger_topic_hex[:8]}…") + call_trigger(helper_addr, trigger_topic_hex) + + # --- Wait for decryption keys --------------------------------------------- + print("Waiting for decryption keys…") + for ip_hex in identity_prefixes: + wait_for_event_decryption_key(keyper_config_index, ip_hex, poll_interval) + + # --- Assert no batching --------------------------------------------------- + print( + "Asserting no-batching (each trigger must produce a distinct identities_hash)…" + ) + assert_no_batching(keyper_config_index, num_triggers) + + print( + f"\nAll {num_triggers} event-based decryption triggers handled without batching. ✓" + ) + + +if __name__ == "__main__": + main() diff --git a/mise-test-setup/mise-tasks/utils.py b/mise-test-setup/mise-tasks/utils.py index 589107ea..ef07fe9e 100644 --- a/mise-test-setup/mise-tasks/utils.py +++ b/mise-test-setup/mise-tasks/utils.py @@ -115,3 +115,41 @@ def get_created_contract_address( if isinstance(address, str) and address: return address return None + + +def get_uups_proxy_address( + deployment_run: dict[str, object], impl_contract_name: str +) -> str | None: + """Return the ERC1967Proxy address deployed for the given UUPS implementation. + + The deployment script deploys the implementation first, then an ERC1967Proxy + whose constructor input encodes the implementation address. We identify the + correct proxy by searching for the ERC1967Proxy CREATE that immediately follows + the implementation and whose input data contains the implementation address. + """ + transactions = deployment_run.get("transactions") + if not isinstance(transactions, list): + return None + for i, tx in enumerate(transactions): + if not isinstance(tx, dict): + continue + if tx.get("contractName") != impl_contract_name: + continue + impl_addr = (tx.get("contractAddress") or "").lower().replace("0x", "") + if not impl_addr: + continue + for j in range(i + 1, len(transactions)): + candidate = transactions[j] + if not isinstance(candidate, dict): + continue + if candidate.get("contractName") != "ERC1967Proxy": + continue + input_data = ( + candidate.get("transaction", {}).get("input") or "" + ).lower() + if impl_addr in input_data: + proxy_addr = candidate.get("contractAddress") + if isinstance(proxy_addr, str) and proxy_addr: + return proxy_addr + break + return None diff --git a/rolling-shutter/keyperimpl/shutterservice/newblock.go b/rolling-shutter/keyperimpl/shutterservice/newblock.go index 8b8dfa46..296ff050 100644 --- a/rolling-shutter/keyperimpl/shutterservice/newblock.go +++ b/rolling-shutter/keyperimpl/shutterservice/newblock.go @@ -218,7 +218,8 @@ func (kpr *Keyper) createTriggersFromIdentityRegisteredEvents( if identityPreimages[event.Eon] == nil { identityPreimages[event.Eon] = make([]identitypreimage.IdentityPreimage, 0) } - identityPreimages[event.Eon] = append(identityPreimages[event.Eon], identitypreimage.IdentityPreimage(event.Identity)) + identityPreimages[event.Eon] = append(identityPreimages[event.Eon], + identitypreimage.IdentityPreimage(event.Identity)) if _, exists := lastEonBlock[event.Eon]; !exists { lastEonBlock[event.Eon] = eon.ActivationBlockNumber @@ -269,19 +270,15 @@ func (kpr *Keyper) prepareEventBasedTriggers(ctx context.Context) ([]epochkghand continue } - identities := []identitypreimage.IdentityPreimage{} + // issue 698: here we "unbatch" all event based decryption triggers and create one broker event per identity for _, firedTrigger := range firedTriggers { - identities = append(identities, firedTrigger.Identity) + identityPreimage := []identitypreimage.IdentityPreimage{firedTrigger.Identity} + decryptionTrigger := epochkghandler.DecryptionTrigger{ + BlockNumber: uint64(eonStruct.ActivationBlockNumber), //nolint:gosec + IdentityPreimages: identityPreimage, + } + decryptionTriggers = append(decryptionTriggers, decryptionTrigger) } - - sortedIdentityPreimages := sortIdentityPreimages(identities) - - decryptionTrigger := epochkghandler.DecryptionTrigger{ - BlockNumber: uint64(eonStruct.ActivationBlockNumber), - IdentityPreimages: sortedIdentityPreimages, - } - - decryptionTriggers = append(decryptionTriggers, decryptionTrigger) } return decryptionTriggers, nil } diff --git a/rolling-shutter/keyperimpl/shutterservice/newblock_test.go b/rolling-shutter/keyperimpl/shutterservice/newblock_test.go index 14fbd040..3b1d4f37 100644 --- a/rolling-shutter/keyperimpl/shutterservice/newblock_test.go +++ b/rolling-shutter/keyperimpl/shutterservice/newblock_test.go @@ -1,7 +1,6 @@ package shutterservice import ( - "bytes" "context" "database/sql" "math" @@ -111,7 +110,9 @@ func TestProcessBlockSuccess(t *testing.T) { select { case ev := <-decryptionTriggerChannel: assert.Equal(t, ev.Value.BlockNumber, activationBlockNumberUint64) - assert.DeepEqual(t, ev.Value.IdentityPreimages, []identitypreimage.IdentityPreimage{identity}) + assert.DeepEqual(t, ev.Value.IdentityPreimages, []identitypreimage.IdentityPreimage{ + identity, + }) case <-time.After(2 * time.Second): t.Fatal("expected decryption trigger") } @@ -455,10 +456,7 @@ func setupEventBasedOrderingTest( ) (*Keyper, *servicedatabase.Queries, int64) { t.Helper() - const keyperIndex = uint64(1) - testsetup.InitializeEon(ctx, t, dbpool, config, keyperIndex) - - privateKey, sender, err := generateRandomAccount() + privateKey, _, err := generateRandomAccount() assert.NilError(t, err) kpr := &Keyper{ @@ -471,12 +469,16 @@ func setupEventBasedOrderingTest( }, }, } - _ = sender + + // Register kpr's address as the keyper-at-index-1 so resolveDecryptableEon + // finds it in the keyper set. + const keyperIndex = uint64(1) + testsetup.InitializeEon(ctx, t, dbpool, &kprAddressTestConfig{addr: kpr.config.GetAddress()}, keyperIndex) return kpr, servicedatabase.New(dbpool), 1 } -func TestFiredTriggersProducesOrderedShares(t *testing.T) { +func TestFiredTriggersProducesUnbatchedDecryptionTriggers(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") } @@ -531,15 +533,14 @@ func TestFiredTriggersProducesOrderedShares(t *testing.T) { triggers, err := kpr.prepareEventBasedTriggers(ctx) assert.NilError(t, err) - assert.Equal(t, len(triggers), 1) - assert.Equal(t, len(triggers[0].IdentityPreimages), len(inserted)) - for i := 1; i < len(triggers[0].IdentityPreimages); i++ { - assert.Assert(t, bytes.Compare( - triggers[0].IdentityPreimages[i-1], - triggers[0].IdentityPreimages[i], - ) < 0) + + // Each fired trigger must produce its own DecryptionTrigger with exactly one identity (no batching). + assert.Equal(t, len(triggers), len(inserted)) + for _, trigger := range triggers { + assert.Equal(t, len(trigger.IdentityPreimages), 1) } + // Verify the first trigger can still be used to construct valid key shares. coreDB := corekeyperdatabase.New(dbpool) triggerBlockNumber := triggers[0].BlockNumber if triggerBlockNumber > math.MaxInt64 { @@ -551,14 +552,16 @@ func TestFiredTriggersProducesOrderedShares(t *testing.T) { keyShareHandler := &epochkghandler.KeyShareHandler{ InstanceID: config.GetInstanceID(), - KeyperAddress: config.GetAddress(), + KeyperAddress: kpr.config.GetAddress(), MaxNumKeysPerMessage: config.GetMaxNumKeysPerMessage(), DBPool: dbpool, } msg, err := keyShareHandler.ConstructDecryptionKeyShares(ctx, triggerEon, triggers[0].IdentityPreimages) assert.NilError(t, err) - validator := epochkghandler.NewDecryptionKeyShareHandler(config, dbpool) + validator := epochkghandler.NewDecryptionKeyShareHandler(&kprAddressTestConfig{ + addr: kpr.config.GetAddress(), + }, dbpool) res, err := validator.ValidateMessage(ctx, msg) assert.Equal(t, res, pubsub.ValidationAccept) assert.NilError(t, err) diff --git a/rolling-shutter/keyperimpl/shutterservice/setup_test.go b/rolling-shutter/keyperimpl/shutterservice/setup_test.go index 093ad8da..50261c54 100644 --- a/rolling-shutter/keyperimpl/shutterservice/setup_test.go +++ b/rolling-shutter/keyperimpl/shutterservice/setup_test.go @@ -33,3 +33,20 @@ func (c *TestConfig) GetMaxNumKeysPerMessage() uint64 { } var _ testsetup.TestConfig = &TestConfig{} + +// kprAddressTestConfig is a TestConfig that substitutes a specific address +// while delegating all other fields to the global config. Used to register +// a dynamically-generated keyper address in InitializeEon. +type kprAddressTestConfig struct { + addr common.Address +} + +func (c *kprAddressTestConfig) GetAddress() common.Address { return c.addr } +func (c *kprAddressTestConfig) GetInstanceID() uint64 { return config.GetInstanceID() } +func (c *kprAddressTestConfig) GetEon() uint64 { return config.GetEon() } +func (c *kprAddressTestConfig) GetCollatorKey() *ecdsa.PrivateKey { return nil } +func (c *kprAddressTestConfig) GetMaxNumKeysPerMessage() uint64 { + return config.GetMaxNumKeysPerMessage() +} + +var _ testsetup.TestConfig = &kprAddressTestConfig{}