diff --git a/.sqlx/query-b94514ce9abc8be15ba3b5a67f33ead0d83e409210ce5e711932bcb32888f2bf.json b/.sqlx/query-29a4feaae95487b9964c8b920bbd33b48a06205011edf8b214045c8a0f898b4c.json similarity index 73% rename from .sqlx/query-b94514ce9abc8be15ba3b5a67f33ead0d83e409210ce5e711932bcb32888f2bf.json rename to .sqlx/query-29a4feaae95487b9964c8b920bbd33b48a06205011edf8b214045c8a0f898b4c.json index 08847c569..d53ace621 100644 --- a/.sqlx/query-b94514ce9abc8be15ba3b5a67f33ead0d83e409210ce5e711932bcb32888f2bf.json +++ b/.sqlx/query-29a4feaae95487b9964c8b920bbd33b48a06205011edf8b214045c8a0f898b4c.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT \n id,\n signature,\n allocation_id,\n payer,\n data_service,\n service_provider,\n timestamp_ns,\n nonce,\n value\n FROM tap_horizon_receipts\n WHERE\n allocation_id = $1\n AND payer = $2\n AND service_provider = $3\n AND signer_address IN (SELECT unnest($4::text[]))\n AND $5::numrange @> timestamp_ns\n ORDER BY timestamp_ns ASC\n LIMIT $6\n ", + "query": "\n SELECT \n id,\n signature,\n collection_id,\n payer,\n data_service,\n service_provider,\n timestamp_ns,\n nonce,\n value\n FROM tap_horizon_receipts\n WHERE\n collection_id = $1\n AND payer = $2\n AND data_service = $3\n AND service_provider = $4\n AND signer_address IN (SELECT unnest($5::text[]))\n AND $6::numrange @> timestamp_ns\n ORDER BY timestamp_ns ASC\n LIMIT $7\n ", "describe": { "columns": [ { @@ -15,7 +15,7 @@ }, { "ordinal": 2, - "name": "allocation_id", + "name": "collection_id", "type_info": "Bpchar" }, { @@ -54,6 +54,7 @@ "Bpchar", "Bpchar", "Bpchar", + "Bpchar", "TextArray", "NumRange", "Int8" @@ -71,5 +72,5 @@ false ] }, - "hash": "b94514ce9abc8be15ba3b5a67f33ead0d83e409210ce5e711932bcb32888f2bf" + "hash": "29a4feaae95487b9964c8b920bbd33b48a06205011edf8b214045c8a0f898b4c" } diff --git a/.sqlx/query-8b904cecdd8e9c5dd0cd8a0dbc2e8a19c2197f08d576e5cbf978e39432bb3d5a.json b/.sqlx/query-42db7ecab1075c348689c94bdfeba5914499e181652d6b8a71fbf4f7d44bf7ac.json similarity index 84% rename from .sqlx/query-8b904cecdd8e9c5dd0cd8a0dbc2e8a19c2197f08d576e5cbf978e39432bb3d5a.json rename to .sqlx/query-42db7ecab1075c348689c94bdfeba5914499e181652d6b8a71fbf4f7d44bf7ac.json index bca31dc48..7808f60c5 100644 --- a/.sqlx/query-8b904cecdd8e9c5dd0cd8a0dbc2e8a19c2197f08d576e5cbf978e39432bb3d5a.json +++ b/.sqlx/query-42db7ecab1075c348689c94bdfeba5914499e181652d6b8a71fbf4f7d44bf7ac.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO tap_horizon_receipts (\n signer_address,\n signature,\n allocation_id,\n payer,\n data_service,\n service_provider,\n timestamp_ns,\n nonce,\n value\n ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n RETURNING id\n ", + "query": "\n INSERT INTO tap_horizon_receipts (\n signer_address,\n signature,\n collection_id,\n payer,\n data_service,\n service_provider,\n timestamp_ns,\n nonce,\n value\n ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n RETURNING id\n ", "describe": { "columns": [ { @@ -26,5 +26,5 @@ false ] }, - "hash": "8b904cecdd8e9c5dd0cd8a0dbc2e8a19c2197f08d576e5cbf978e39432bb3d5a" + "hash": "42db7ecab1075c348689c94bdfeba5914499e181652d6b8a71fbf4f7d44bf7ac" } diff --git a/.sqlx/query-cd279b9b74e3efdb79ece086b1e6713d2fc766e6553568df8c6ab1d39a5282f6.json b/.sqlx/query-4d512a6842224fc372eb3eb65b8a86cc4f5f31098afea4f5546fc7a3e35132db.json similarity index 59% rename from .sqlx/query-cd279b9b74e3efdb79ece086b1e6713d2fc766e6553568df8c6ab1d39a5282f6.json rename to .sqlx/query-4d512a6842224fc372eb3eb65b8a86cc4f5f31098afea4f5546fc7a3e35132db.json index 6a50789b9..e64f75b1a 100644 --- a/.sqlx/query-cd279b9b74e3efdb79ece086b1e6713d2fc766e6553568df8c6ab1d39a5282f6.json +++ b/.sqlx/query-4d512a6842224fc372eb3eb65b8a86cc4f5f31098afea4f5546fc7a3e35132db.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n DELETE FROM tap_horizon_receipts\n WHERE\n allocation_id = $1\n AND signer_address IN (SELECT unnest($2::text[]))\n AND $3::numrange @> timestamp_ns\n AND payer = $4\n AND service_provider = $5\n ", + "query": "\n DELETE FROM tap_horizon_receipts\n WHERE\n collection_id = $1\n AND signer_address IN (SELECT unnest($2::text[]))\n AND $3::numrange @> timestamp_ns\n AND payer = $4\n AND data_service = $5\n AND service_provider = $6\n ", "describe": { "columns": [], "parameters": { @@ -9,10 +9,11 @@ "TextArray", "NumRange", "Bpchar", + "Bpchar", "Bpchar" ] }, "nullable": [] }, - "hash": "cd279b9b74e3efdb79ece086b1e6713d2fc766e6553568df8c6ab1d39a5282f6" + "hash": "4d512a6842224fc372eb3eb65b8a86cc4f5f31098afea4f5546fc7a3e35132db" } diff --git a/.sqlx/query-27a9c5a16714c8551c8921e4012bceda54664d0f41228f60b3a0f7f601cf9923.json b/.sqlx/query-4f841a3df3b3774658b7aa68e68acc0b8ef122bd08f064338d23e3061cfe402a.json similarity index 76% rename from .sqlx/query-27a9c5a16714c8551c8921e4012bceda54664d0f41228f60b3a0f7f601cf9923.json rename to .sqlx/query-4f841a3df3b3774658b7aa68e68acc0b8ef122bd08f064338d23e3061cfe402a.json index 6a5e4b13a..673b3386d 100644 --- a/.sqlx/query-27a9c5a16714c8551c8921e4012bceda54664d0f41228f60b3a0f7f601cf9923.json +++ b/.sqlx/query-4f841a3df3b3774658b7aa68e68acc0b8ef122bd08f064338d23e3061cfe402a.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT allocation_id, value_aggregate\n FROM tap_horizon_ravs\n WHERE payer = $1 AND last AND NOT final;\n ", + "query": "\n SELECT collection_id, value_aggregate\n FROM tap_horizon_ravs\n WHERE payer = $1 AND last AND NOT final;\n ", "describe": { "columns": [ { "ordinal": 0, - "name": "allocation_id", + "name": "collection_id", "type_info": "Bpchar" }, { @@ -24,5 +24,5 @@ false ] }, - "hash": "27a9c5a16714c8551c8921e4012bceda54664d0f41228f60b3a0f7f601cf9923" + "hash": "4f841a3df3b3774658b7aa68e68acc0b8ef122bd08f064338d23e3061cfe402a" } diff --git a/.sqlx/query-f02805d423945f35b93dde58db49872832048b338237c786727a6c9173e23b38.json b/.sqlx/query-75f47b9ce3247efa6cdfaa66fae4777756c14e25b666de3f2dbcc6ab51732dbe.json similarity index 79% rename from .sqlx/query-f02805d423945f35b93dde58db49872832048b338237c786727a6c9173e23b38.json rename to .sqlx/query-75f47b9ce3247efa6cdfaa66fae4777756c14e25b666de3f2dbcc6ab51732dbe.json index b6cf1c1c6..31f5b3a46 100644 --- a/.sqlx/query-f02805d423945f35b93dde58db49872832048b338237c786727a6c9173e23b38.json +++ b/.sqlx/query-75f47b9ce3247efa6cdfaa66fae4777756c14e25b666de3f2dbcc6ab51732dbe.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT \n signature,\n allocation_id,\n payer,\n data_service,\n service_provider,\n timestamp_ns,\n value_aggregate,\n metadata\n FROM tap_horizon_ravs\n WHERE \n allocation_id = $1 \n AND payer = $2\n AND service_provider = $3\n ", + "query": "\n SELECT \n signature,\n collection_id,\n payer,\n data_service,\n service_provider,\n timestamp_ns,\n value_aggregate,\n metadata\n FROM tap_horizon_ravs\n WHERE \n collection_id = $1 \n AND payer = $2\n AND data_service = $3\n AND service_provider = $4\n ", "describe": { "columns": [ { @@ -10,7 +10,7 @@ }, { "ordinal": 1, - "name": "allocation_id", + "name": "collection_id", "type_info": "Bpchar" }, { @@ -46,6 +46,7 @@ ], "parameters": { "Left": [ + "Bpchar", "Bpchar", "Bpchar", "Bpchar" @@ -62,5 +63,5 @@ false ] }, - "hash": "f02805d423945f35b93dde58db49872832048b338237c786727a6c9173e23b38" + "hash": "75f47b9ce3247efa6cdfaa66fae4777756c14e25b666de3f2dbcc6ab51732dbe" } diff --git a/.sqlx/query-386e6b18da62b478ccdba5439490817f582abaec68e1ba01d55daee1e1edcbbd.json b/.sqlx/query-7c7ebe97ae6a5b65fc438715a428b82b2a2dc5e1f0f29f4e6c7b2e498f3b1d82.json similarity index 79% rename from .sqlx/query-386e6b18da62b478ccdba5439490817f582abaec68e1ba01d55daee1e1edcbbd.json rename to .sqlx/query-7c7ebe97ae6a5b65fc438715a428b82b2a2dc5e1f0f29f4e6c7b2e498f3b1d82.json index 0dd3c43d1..a861ecf83 100644 --- a/.sqlx/query-386e6b18da62b478ccdba5439490817f582abaec68e1ba01d55daee1e1edcbbd.json +++ b/.sqlx/query-7c7ebe97ae6a5b65fc438715a428b82b2a2dc5e1f0f29f4e6c7b2e498f3b1d82.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n payer,\n ARRAY_AGG(DISTINCT allocation_id) FILTER (WHERE NOT last) AS allocation_ids\n FROM tap_horizon_ravs\n GROUP BY payer\n ", + "query": "\n SELECT\n payer,\n ARRAY_AGG(DISTINCT collection_id) FILTER (WHERE NOT last) AS allocation_ids\n FROM tap_horizon_ravs\n GROUP BY payer\n ", "describe": { "columns": [ { @@ -22,5 +22,5 @@ null ] }, - "hash": "386e6b18da62b478ccdba5439490817f582abaec68e1ba01d55daee1e1edcbbd" + "hash": "7c7ebe97ae6a5b65fc438715a428b82b2a2dc5e1f0f29f4e6c7b2e498f3b1d82" } diff --git a/.sqlx/query-f65b5a6c2149155d69fb1f187ebb4e24c08d5763d97da4e2ec759c30d6170a68.json b/.sqlx/query-7fdec080f1f133137e3ccc9e8792f41fdafc10375772b4d2f284c0c62995d15c.json similarity index 82% rename from .sqlx/query-f65b5a6c2149155d69fb1f187ebb4e24c08d5763d97da4e2ec759c30d6170a68.json rename to .sqlx/query-7fdec080f1f133137e3ccc9e8792f41fdafc10375772b4d2f284c0c62995d15c.json index 71f5c8e0b..c3042f79a 100644 --- a/.sqlx/query-f65b5a6c2149155d69fb1f187ebb4e24c08d5763d97da4e2ec759c30d6170a68.json +++ b/.sqlx/query-7fdec080f1f133137e3ccc9e8792f41fdafc10375772b4d2f284c0c62995d15c.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO tap_horizon_receipts_invalid (\n signer_address,\n signature,\n allocation_id,\n payer,\n data_service,\n service_provider,\n timestamp_ns,\n nonce,\n value,\n error_log\n ) SELECT * FROM UNNEST(\n $1::CHAR(40)[],\n $2::BYTEA[],\n $3::CHAR(40)[],\n $4::CHAR(40)[],\n $5::CHAR(40)[],\n $6::CHAR(40)[],\n $7::NUMERIC(20)[],\n $8::NUMERIC(20)[],\n $9::NUMERIC(40)[],\n $10::TEXT[]\n )", + "query": "INSERT INTO tap_horizon_receipts_invalid (\n signer_address,\n signature,\n collection_id,\n payer,\n data_service,\n service_provider,\n timestamp_ns,\n nonce,\n value,\n error_log\n ) SELECT * FROM UNNEST(\n $1::CHAR(40)[],\n $2::BYTEA[],\n $3::CHAR(64)[],\n $4::CHAR(40)[],\n $5::CHAR(40)[],\n $6::CHAR(40)[],\n $7::NUMERIC(20)[],\n $8::NUMERIC(20)[],\n $9::NUMERIC(40)[],\n $10::TEXT[]\n )", "describe": { "columns": [], "parameters": { @@ -19,5 +19,5 @@ }, "nullable": [] }, - "hash": "f65b5a6c2149155d69fb1f187ebb4e24c08d5763d97da4e2ec759c30d6170a68" + "hash": "7fdec080f1f133137e3ccc9e8792f41fdafc10375772b4d2f284c0c62995d15c" } diff --git a/.sqlx/query-7bf9c412120de537eecb8efb64da5b4ace9acc032be502cd1d9fc72c5b9ed50a.json b/.sqlx/query-a72b8dfdc55b332e4f78ce1fb9b5f32074075a4bb5e27005c5265d38a8487653.json similarity index 66% rename from .sqlx/query-7bf9c412120de537eecb8efb64da5b4ace9acc032be502cd1d9fc72c5b9ed50a.json rename to .sqlx/query-a72b8dfdc55b332e4f78ce1fb9b5f32074075a4bb5e27005c5265d38a8487653.json index ea67b93bb..0960e779e 100644 --- a/.sqlx/query-7bf9c412120de537eecb8efb64da5b4ace9acc032be502cd1d9fc72c5b9ed50a.json +++ b/.sqlx/query-a72b8dfdc55b332e4f78ce1fb9b5f32074075a4bb5e27005c5265d38a8487653.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n WITH grouped AS (\n SELECT signer_address, allocation_id\n FROM tap_horizon_receipts\n GROUP BY signer_address, allocation_id\n )\n SELECT \n signer_address,\n ARRAY_AGG(allocation_id) AS allocation_ids\n FROM grouped\n GROUP BY signer_address\n ", + "query": "\n WITH grouped AS (\n SELECT signer_address, collection_id\n FROM tap_horizon_receipts\n GROUP BY signer_address, collection_id\n )\n SELECT \n signer_address,\n ARRAY_AGG(collection_id) AS collection_ids\n FROM grouped\n GROUP BY signer_address\n ", "describe": { "columns": [ { @@ -10,7 +10,7 @@ }, { "ordinal": 1, - "name": "allocation_ids", + "name": "collection_ids", "type_info": "BpcharArray" } ], @@ -22,5 +22,5 @@ null ] }, - "hash": "7bf9c412120de537eecb8efb64da5b4ace9acc032be502cd1d9fc72c5b9ed50a" + "hash": "a72b8dfdc55b332e4f78ce1fb9b5f32074075a4bb5e27005c5265d38a8487653" } diff --git a/.sqlx/query-5ee76e76f4ace2dbab69fd58eaf7f0539d2ffab1289130555dba0f6fdc3065ff.json b/.sqlx/query-b5c7a7f7d9fc5e06ee54bc5cce913431e1a0c922742d1c7a14e828f5953713f5.json similarity index 82% rename from .sqlx/query-5ee76e76f4ace2dbab69fd58eaf7f0539d2ffab1289130555dba0f6fdc3065ff.json rename to .sqlx/query-b5c7a7f7d9fc5e06ee54bc5cce913431e1a0c922742d1c7a14e828f5953713f5.json index 5b1b624d1..87f6288b3 100644 --- a/.sqlx/query-5ee76e76f4ace2dbab69fd58eaf7f0539d2ffab1289130555dba0f6fdc3065ff.json +++ b/.sqlx/query-b5c7a7f7d9fc5e06ee54bc5cce913431e1a0c922742d1c7a14e828f5953713f5.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO tap_horizon_ravs (\n payer,\n data_service,\n service_provider,\n metadata,\n signature,\n allocation_id,\n timestamp_ns,\n value_aggregate,\n created_at,\n updated_at\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $9)\n ON CONFLICT (payer, data_service, service_provider, allocation_id)\n DO UPDATE SET\n signature = $5,\n timestamp_ns = $7,\n value_aggregate = $8,\n updated_at = $9,\n metadata = $4\n ", + "query": "\n INSERT INTO tap_horizon_ravs (\n payer,\n data_service,\n service_provider,\n metadata,\n signature,\n collection_id,\n timestamp_ns,\n value_aggregate,\n created_at,\n updated_at\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $9)\n ON CONFLICT (payer, data_service, service_provider, collection_id)\n DO UPDATE SET\n signature = $5,\n timestamp_ns = $7,\n value_aggregate = $8,\n updated_at = $9,\n metadata = $4\n ", "describe": { "columns": [], "parameters": { @@ -18,5 +18,5 @@ }, "nullable": [] }, - "hash": "5ee76e76f4ace2dbab69fd58eaf7f0539d2ffab1289130555dba0f6fdc3065ff" + "hash": "b5c7a7f7d9fc5e06ee54bc5cce913431e1a0c922742d1c7a14e828f5953713f5" } diff --git a/.sqlx/query-fa31900971e23fdf98dd48fcd649e04328fb1e758a0b538319c5fb132251a7ed.json b/.sqlx/query-bb4ba42f2eb9357b0dbad6aeed8aac18e3ce8b5f750cbf9525813724ad5f06f4.json similarity index 86% rename from .sqlx/query-fa31900971e23fdf98dd48fcd649e04328fb1e758a0b538319c5fb132251a7ed.json rename to .sqlx/query-bb4ba42f2eb9357b0dbad6aeed8aac18e3ce8b5f750cbf9525813724ad5f06f4.json index e87b866a2..af5b91ecb 100644 --- a/.sqlx/query-fa31900971e23fdf98dd48fcd649e04328fb1e758a0b538319c5fb132251a7ed.json +++ b/.sqlx/query-bb4ba42f2eb9357b0dbad6aeed8aac18e3ce8b5f750cbf9525813724ad5f06f4.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n MAX(id),\n SUM(value),\n COUNT(*)\n FROM\n tap_horizon_receipts\n WHERE\n allocation_id = $1\n AND service_provider = $2\n AND id <= $3\n AND signer_address IN (SELECT unnest($4::text[]))\n AND timestamp_ns > $5\n ", + "query": "\n SELECT\n MAX(id),\n SUM(value),\n COUNT(*)\n FROM\n tap_horizon_receipts\n WHERE\n collection_id = $1\n AND service_provider = $2\n AND id <= $3\n AND signer_address IN (SELECT unnest($4::text[]))\n AND timestamp_ns > $5\n ", "describe": { "columns": [ { @@ -34,5 +34,5 @@ null ] }, - "hash": "fa31900971e23fdf98dd48fcd649e04328fb1e758a0b538319c5fb132251a7ed" + "hash": "bb4ba42f2eb9357b0dbad6aeed8aac18e3ce8b5f750cbf9525813724ad5f06f4" } diff --git a/.sqlx/query-b467894d499541f2bca15965f38ee551ece8619860f3fbcb184ebded33898cbd.json b/.sqlx/query-c6a31bb2651621e5daad8520afde9d9f2fdca5214dcd737f14c7be4f29d23db9.json similarity index 84% rename from .sqlx/query-b467894d499541f2bca15965f38ee551ece8619860f3fbcb184ebded33898cbd.json rename to .sqlx/query-c6a31bb2651621e5daad8520afde9d9f2fdca5214dcd737f14c7be4f29d23db9.json index 76efd9841..a37c64bc8 100644 --- a/.sqlx/query-b467894d499541f2bca15965f38ee551ece8619860f3fbcb184ebded33898cbd.json +++ b/.sqlx/query-c6a31bb2651621e5daad8520afde9d9f2fdca5214dcd737f14c7be4f29d23db9.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n MAX(id),\n SUM(value),\n COUNT(*)\n FROM\n tap_horizon_receipts_invalid\n WHERE\n allocation_id = $1\n AND signer_address IN (SELECT unnest($2::text[]))\n ", + "query": "\n SELECT\n MAX(id),\n SUM(value),\n COUNT(*)\n FROM\n tap_horizon_receipts_invalid\n WHERE\n collection_id = $1\n AND signer_address IN (SELECT unnest($2::text[]))\n ", "describe": { "columns": [ { @@ -31,5 +31,5 @@ null ] }, - "hash": "b467894d499541f2bca15965f38ee551ece8619860f3fbcb184ebded33898cbd" + "hash": "c6a31bb2651621e5daad8520afde9d9f2fdca5214dcd737f14c7be4f29d23db9" } diff --git a/.sqlx/query-9a11e8e814e42113c91050acf54c2e847f24a2e086abcbe8e7be618f84d58f78.json b/.sqlx/query-cb8f0add5e9dd8122cdced4c89836f542234c06c237f7fa8aa84602cb75b0622.json similarity index 73% rename from .sqlx/query-9a11e8e814e42113c91050acf54c2e847f24a2e086abcbe8e7be618f84d58f78.json rename to .sqlx/query-cb8f0add5e9dd8122cdced4c89836f542234c06c237f7fa8aa84602cb75b0622.json index 134eef5e2..51ba8772c 100644 --- a/.sqlx/query-9a11e8e814e42113c91050acf54c2e847f24a2e086abcbe8e7be618f84d58f78.json +++ b/.sqlx/query-cb8f0add5e9dd8122cdced4c89836f542234c06c237f7fa8aa84602cb75b0622.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE tap_horizon_ravs\n SET last = true\n WHERE \n allocation_id = $1\n AND payer = $2\n AND service_provider = $3\n ", + "query": "\n UPDATE tap_horizon_ravs\n SET last = true\n WHERE \n collection_id = $1\n AND payer = $2\n AND service_provider = $3\n ", "describe": { "columns": [], "parameters": { @@ -12,5 +12,5 @@ }, "nullable": [] }, - "hash": "9a11e8e814e42113c91050acf54c2e847f24a2e086abcbe8e7be618f84d58f78" + "hash": "cb8f0add5e9dd8122cdced4c89836f542234c06c237f7fa8aa84602cb75b0622" } diff --git a/.sqlx/query-6ce2133a20924d5098fa2e27f897e871955e9eb8a0a6f8b9f23a3f38597793f6.json b/.sqlx/query-d58cd87daf6fa755debd34779d61972cfe8f7b0ee11a8bbac0a70304d71e510c.json similarity index 88% rename from .sqlx/query-6ce2133a20924d5098fa2e27f897e871955e9eb8a0a6f8b9f23a3f38597793f6.json rename to .sqlx/query-d58cd87daf6fa755debd34779d61972cfe8f7b0ee11a8bbac0a70304d71e510c.json index d3b8f8181..6f45961ba 100644 --- a/.sqlx/query-6ce2133a20924d5098fa2e27f897e871955e9eb8a0a6f8b9f23a3f38597793f6.json +++ b/.sqlx/query-d58cd87daf6fa755debd34779d61972cfe8f7b0ee11a8bbac0a70304d71e510c.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT \n signature,\n allocation_id,\n payer,\n data_service,\n service_provider,\n timestamp_ns,\n nonce,\n value\n FROM tap_horizon_receipts\n ", + "query": "\n SELECT \n signature,\n collection_id,\n payer,\n data_service,\n service_provider,\n timestamp_ns,\n nonce,\n value\n FROM tap_horizon_receipts\n ", "describe": { "columns": [ { @@ -10,7 +10,7 @@ }, { "ordinal": 1, - "name": "allocation_id", + "name": "collection_id", "type_info": "Bpchar" }, { @@ -58,5 +58,5 @@ false ] }, - "hash": "6ce2133a20924d5098fa2e27f897e871955e9eb8a0a6f8b9f23a3f38597793f6" + "hash": "d58cd87daf6fa755debd34779d61972cfe8f7b0ee11a8bbac0a70304d71e510c" } diff --git a/.sqlx/query-e97e62b85a4d6f5bc61b33f6f8c927f6e8cc5a3b867a77da25527a0c94bd99d0.json b/.sqlx/query-e97e62b85a4d6f5bc61b33f6f8c927f6e8cc5a3b867a77da25527a0c94bd99d0.json new file mode 100644 index 000000000..e0181c7c1 --- /dev/null +++ b/.sqlx/query-e97e62b85a4d6f5bc61b33f6f8c927f6e8cc5a3b867a77da25527a0c94bd99d0.json @@ -0,0 +1,18 @@ +{ + "db_name": "PostgreSQL", + "query": "\n DELETE FROM tap_horizon_receipts\n WHERE timestamp_ns BETWEEN $1 AND $2\n AND collection_id = $3\n AND service_provider = $4\n AND signer_address IN (SELECT unnest($5::text[]));\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Numeric", + "Numeric", + "Bpchar", + "Bpchar", + "TextArray" + ] + }, + "nullable": [] + }, + "hash": "e97e62b85a4d6f5bc61b33f6f8c927f6e8cc5a3b867a77da25527a0c94bd99d0" +} diff --git a/.sqlx/query-eef0d4e41d62691f9987cf4a086da379c5d5c363f378b31f226444a6b808a239.json b/.sqlx/query-ff1c4e68e51d5a5dce96d594bb55dd07d642bff324018ef44e398785f85152c1.json similarity index 75% rename from .sqlx/query-eef0d4e41d62691f9987cf4a086da379c5d5c363f378b31f226444a6b808a239.json rename to .sqlx/query-ff1c4e68e51d5a5dce96d594bb55dd07d642bff324018ef44e398785f85152c1.json index 9d39f1c65..adeefa1ec 100644 --- a/.sqlx/query-eef0d4e41d62691f9987cf4a086da379c5d5c363f378b31f226444a6b808a239.json +++ b/.sqlx/query-ff1c4e68e51d5a5dce96d594bb55dd07d642bff324018ef44e398785f85152c1.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO tap_horizon_receipts (\n signer_address,\n signature,\n allocation_id,\n payer,\n data_service,\n service_provider,\n timestamp_ns,\n nonce,\n value\n ) SELECT * FROM UNNEST(\n $1::CHAR(40)[],\n $2::BYTEA[],\n $3::CHAR(40)[],\n $4::CHAR(40)[],\n $5::CHAR(40)[],\n $6::CHAR(40)[],\n $7::NUMERIC(20)[],\n $8::NUMERIC(20)[],\n $9::NUMERIC(40)[]\n )", + "query": "INSERT INTO tap_horizon_receipts (\n signer_address,\n signature,\n collection_id,\n payer,\n data_service,\n service_provider,\n timestamp_ns,\n nonce,\n value\n ) SELECT * FROM UNNEST(\n $1::CHAR(40)[],\n $2::BYTEA[],\n $3::CHAR(64)[],\n $4::CHAR(40)[],\n $5::CHAR(40)[],\n $6::CHAR(40)[],\n $7::NUMERIC(20)[],\n $8::NUMERIC(20)[],\n $9::NUMERIC(40)[]\n )", "describe": { "columns": [], "parameters": { @@ -18,5 +18,5 @@ }, "nullable": [] }, - "hash": "eef0d4e41d62691f9987cf4a086da379c5d5c363f378b31f226444a6b808a239" + "hash": "ff1c4e68e51d5a5dce96d594bb55dd07d642bff324018ef44e398785f85152c1" } diff --git a/Cargo.lock b/Cargo.lock index 18312ef9d..5f7d56dcf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,9 +23,9 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "ahash" @@ -77,9 +77,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy" -version = "1.0.8" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca940218f168ba7dd97c22fccd3055e9f2ff463bd5aededf614f1fba71c90648" +checksum = "9f5bedd6a59a2bd3a2f1cb7ff488549a2004302edca4df4d578bf0a814888615" dependencies = [ "alloy-consensus", "alloy-contract", @@ -102,9 +102,9 @@ dependencies = [ [[package]] name = "alloy-chains" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "517e5acbd38b6d4c59da380e8bbadc6d365bf001903ce46cf5521c53c647e07b" +checksum = "19a9cc9d81ace3da457883b0bdf76776e55f1b84219a9e9d55c27ad308548d3f" dependencies = [ "alloy-primitives", "num_enum", @@ -113,15 +113,16 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.0.8" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78090ff96d0d1b648dbcebc63b5305296b76ad4b5d4810f755d7d1224ced6247" +checksum = "d8b77018eec2154eb158869f9f2914a3ea577adf87b11be2764d4795d5ccccf7" dependencies = [ "alloy-eips", "alloy-primitives", "alloy-rlp", "alloy-serde", "alloy-trie", + "alloy-tx-macros", "auto_impl", "c-kzg", "derive_more", @@ -137,9 +138,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "1.0.8" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcdfc3b2f202e3c6284685e6d3dcfbb532b39552d9e1021276e68e2389037616" +checksum = "65bf8b058ff364d6e94bcd2979d7da1862e94d2987065a4eb41fa9eac36e028a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -151,9 +152,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "1.0.8" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f4a5b6c7829e8aa048f5b23defa21706b675e68e612cf88d9f509771fecc806" +checksum = "049ed4836d368929d7c5e206bab2e8d92f00524222edc0026c6bf2a3cb8a02d5" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -167,14 +168,15 @@ dependencies = [ "alloy-transport", "futures", "futures-util", + "serde_json", "thiserror 2.0.12", ] [[package]] name = "alloy-core" -version = "1.1.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3c5a28f166629752f2e7246b813cdea3243cca59aab2d4264b1fd68392c10eb" +checksum = "ad31216895d27d307369daa1393f5850b50bbbd372478a9fa951c095c210627e" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -185,9 +187,9 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "1.1.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18cc14d832bc3331ca22a1c7819de1ede99f58f61a7d123952af7dde8de124a6" +checksum = "7b95b3deca680efc7e9cba781f1a1db352fa1ea50e6384a514944dcf4419e652" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -238,9 +240,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "1.0.8" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb7646210355c36b07886c91cac52e4727191e2b0ee1415cce8f953f6019dd2" +checksum = "33d134f3ac4926124eaf521a1031d11ea98816df3d39fc446fcfd6b36884603f" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -258,9 +260,9 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "1.0.8" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b14b506d7a4f739dd57ad5026d65eb64d842f4e971f71da5e9be5067ecbdc9" +checksum = "fb1c2792605e648bdd1fddcfed8ce0d39d3db495c71d2240cb53df8aee8aea1f" dependencies = [ "alloy-eips", "alloy-primitives", @@ -271,9 +273,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "1.1.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ccaa79753d7bf15f06399ea76922afbfaf8d18bebed9e8fc452984b4a90dcc9" +checksum = "15516116086325c157c18261d768a20677f0f699348000ed391d4ad0dcb82530" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -283,12 +285,13 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.0.8" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a7ed339a633ba1a2af3eb9847dc90936d1b3c380a223cfca7a45be1713d8ab0" +checksum = "31cfdacfeb6b6b40bf6becf92e69e575c68c9f80311c3961d019e29c0b8d6be2" dependencies = [ "alloy-primitives", "alloy-sol-types", + "http 1.3.1", "serde", "serde_json", "thiserror 2.0.12", @@ -297,9 +300,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "1.0.8" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "691a4825b3d08f031b49aae3c11cb35abf2af376fc11146bf8e5930a432dbf40" +checksum = "de68a3f09cd9ab029cf87d08630e1336ca9a530969689fd151d505fa888a2603" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -323,9 +326,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "1.0.8" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5713f40f9cbe4428292d095e8bbb38af82e63ad4247418b7f6d6fb7ef2d9d68b" +checksum = "fcc2689c8addfc43461544d07a6f5f3a3e1f5f4efae61206cb5783dc383cfc8f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -336,9 +339,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "1.1.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18c35fc4b03ace65001676358ffbbaefe2a2b27ee50fe777c345082c7c888be8" +checksum = "6177ed26655d4e84e00b65cb494d4e0b8830e7cae7ef5d63087d445a2600fb55" dependencies = [ "alloy-rlp", "bytes", @@ -346,8 +349,8 @@ dependencies = [ "const-hex", "derive_more", "foldhash", - "hashbrown 0.15.3", - "indexmap 2.9.0", + "hashbrown 0.15.4", + "indexmap 2.10.0", "itoa", "k256", "keccak-asm", @@ -363,9 +366,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "1.0.8" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1382ef9e0fa1ab3f5a3dbc0a0fa1193f3794d5c9d75fc22654bb6da1cf7a59cc" +checksum = "8ced931220f547d30313530ad315654b7862ef52631c90ab857d792865f84a7d" dependencies = [ "alloy-chains", "alloy-consensus", @@ -387,6 +390,7 @@ dependencies = [ "either", "futures", "futures-utils-wasm", + "http 1.3.1", "lru", "parking_lot", "pin-project 1.1.10", @@ -419,14 +423,14 @@ checksum = "64b728d511962dda67c1bc7ea7c03736ec275ed2cf4c35d9585298ac9ccf3b73" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "alloy-rpc-client" -version = "1.0.8" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859ec46fb132175969a0101bdd2fe9ecd413c40feeb0383e98710a4a089cee77" +checksum = "6d1d1eac6e48b772c7290f0f79211a0e822a38b057535b514cc119abd857d5b6" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -449,9 +453,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "1.0.8" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1512ec542339a72c263570644a56d685f20ce77be465fbd3f3f33fb772bcbd" +checksum = "8589c6ae318fcc9624d42e9166f7f82b630d9ad13e180c52addf20b93a8af266" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -461,9 +465,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "1.0.8" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87236623aafabbf7196bcde37a4d626c3e56b3b22d787310e6d5ea25239c5d8" +checksum = "02cfd7ecb21a1bfe68ac6b551172e4d41f828bcc33a2e1563a65d482d4efc1cf" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -472,9 +476,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "1.0.8" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b8acc64d23e484a0a27375b57caba34569729560a29aa366933f0ae07b7786f" +checksum = "bb082c325bdfd05a7c71f52cd1060e62491fbf6edf55962720bdc380847b0784" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -492,9 +496,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.0.8" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "114c287eb4595f1e0844800efb0860dd7228fcf9bc77d52e303fb7a43eb766b2" +checksum = "c7f26c17270c2ac1bd555c4304fe067639f0ddafdd3c8d07a200b2bb5a326e03" dependencies = [ "alloy-primitives", "serde", @@ -503,9 +507,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.0.8" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afebd60fa84d9ce793326941509d8f26ce7b383f2aabd7a42ba215c1b92ea96b" +checksum = "5d9fd649d6ed5b8d7e5014e01758efb937e8407124b182a7f711bf487a1a2697" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -520,9 +524,9 @@ dependencies = [ [[package]] name = "alloy-signer-aws" -version = "1.0.8" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612230fd96c0b0e40fa47e04dee93ba34ba478b036a2197606cbff86b9ddfa88" +checksum = "c37e31847f58dddc3427dd3085efbb4891aa07d02140684c80086163d84a283b" dependencies = [ "alloy-consensus", "alloy-network", @@ -538,9 +542,9 @@ dependencies = [ [[package]] name = "alloy-signer-gcp" -version = "1.0.8" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71f91055102eacf86f9e9300c35834878bf08b2c001d49656f7174ff033facc2" +checksum = "4dc1c04db2ea491075b0ba9d09f292875974122460c28c13db908dad5124818c" dependencies = [ "alloy-consensus", "alloy-network", @@ -556,9 +560,9 @@ dependencies = [ [[package]] name = "alloy-signer-ledger" -version = "1.0.8" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb86fa9a736168469a04e0cdbf5745b3add9eb037efb954241905e5663ba617" +checksum = "b957c7f64d22fb5a31fb2676a832da11513fcdd3a83400c8f1e965e71d466c14" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -576,9 +580,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "1.0.8" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f551042c11c4fa7cb8194d488250b8dc58035241c418d79f07980c4aee4fa5c9" +checksum = "c288c5b38be486bb84986701608f5d815183de990e884bb747f004622783e125" dependencies = [ "alloy-consensus", "alloy-network", @@ -594,42 +598,42 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "1.1.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8612e0658964d616344f199ab251a49d48113992d81b92dab93ed855faa66383" +checksum = "a14f21d053aea4c6630687c2f4ad614bed4c81e14737a9b904798b24f30ea849" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "alloy-sol-macro-expander" -version = "1.1.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a384edac7283bc4c010a355fb648082860c04b826bb7a814c45263c8f304c74" +checksum = "34d99282e7c9ef14eb62727981a985a01869e586d1dec729d3bb33679094c100" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", "const-hex", "heck 0.5.0", - "indexmap 2.9.0", + "indexmap 2.10.0", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", "syn-solidity", "tiny-keccak", ] [[package]] name = "alloy-sol-macro-input" -version = "1.1.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd588c2d516da7deb421b8c166dc60b7ae31bca5beea29ab6621fcfa53d6ca5" +checksum = "eda029f955b78e493360ee1d7bd11e1ab9f2a220a5715449babc79d6d0a01105" dependencies = [ "alloy-json-abi", "const-hex", @@ -639,15 +643,15 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.101", + "syn 2.0.104", "syn-solidity", ] [[package]] name = "alloy-sol-type-parser" -version = "1.1.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86ddeb70792c7ceaad23e57d52250107ebbb86733e52f4a25d8dc1abc931837" +checksum = "10db1bd7baa35bc8d4a1b07efbf734e73e5ba09f2580fb8cee3483a36087ceb2" dependencies = [ "serde", "winnow", @@ -655,9 +659,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "1.1.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "584cb97bfc5746cb9dcc4def77da11694b5d6d7339be91b7480a6a68dc129387" +checksum = "58377025a47d8b8426b3e4846a251f2c1991033b27f517aade368146f6ab1dfe" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -667,9 +671,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.0.8" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fb766c0bce9f62779a83048ca6d998c2ced4153d694027c66e537629f4fd61" +checksum = "e1b790b89e31e183ae36ac0a1419942e21e94d745066f5281417c3e4299ea39e" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -690,9 +694,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.0.8" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "254bd59ca1abaf2da3e3201544a41924163b019414cce16f0dc6bc75d20c6612" +checksum = "f643645a33a681d09ac1ca2112014c2ca09c68aad301da4400484d59c746bc70" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -705,9 +709,9 @@ dependencies = [ [[package]] name = "alloy-trie" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "983d99aa81f586cef9dae38443245e585840fcf0fc58b09aee0b1f27aed1d500" +checksum = "bada1fc392a33665de0dc50d401a3701b62583c655e3522a323490a5da016962" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -719,6 +723,19 @@ dependencies = [ "tracing", ] +[[package]] +name = "alloy-tx-macros" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ef40a046b9bf141afc440cef596c79292708aade57c450dc74e843270fd8e7" +dependencies = [ + "alloy-primitives", + "darling", + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -736,9 +753,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -751,33 +768,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.8" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", "once_cell_polyfill", @@ -953,9 +970,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.23" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b37fc50485c4f3f736a4fb14199f6d5f5ba008d7f28fe710306c92780f004c07" +checksum = "40f6024f3f856663b45fd0c9b6f2024034a702f453549449e0d84a305900dad4" dependencies = [ "flate2", "futures-core", @@ -981,7 +998,7 @@ dependencies = [ "futures-timer", "futures-util", "http 1.3.1", - "indexmap 2.9.0", + "indexmap 2.10.0", "mime", "multer", "num-traits", @@ -1024,7 +1041,7 @@ dependencies = [ "proc-macro2", "quote", "strum 0.26.3", - "syn 2.0.101", + "syn 2.0.104", "thiserror 1.0.69", ] @@ -1047,7 +1064,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34ecdaff7c9cffa3614a9f9999bf9ee4c3078fe3ce4d6a6e161736b56febf2de" dependencies = [ "bytes", - "indexmap 2.9.0", + "indexmap 2.10.0", "serde", "serde_json", ] @@ -1071,7 +1088,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1082,7 +1099,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1096,9 +1113,9 @@ dependencies = [ [[package]] name = "atomic" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" dependencies = [ "bytemuck", ] @@ -1117,14 +1134,14 @@ checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "autometrics" @@ -1156,7 +1173,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1173,9 +1190,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.5.7" +version = "1.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c4063282c69991e57faab9e5cb21ae557e59f5b0fb285c196335243df8dc25c" +checksum = "4f6c68419d8ba16d9a7463671593c54f81ba58cab466e9b759418da606dcc2e2" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -1197,9 +1214,9 @@ dependencies = [ [[package]] name = "aws-sdk-kms" -version = "1.71.0" +version = "1.76.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de993b5250e1aa051f4091ab772ce164de8c078ee9793fdee033b20f7d371ad" +checksum = "ddea79bde6e73fdcba2f9f9640cb0402948d5679fd92ab49d3296a9ce5d1bfa9" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1219,9 +1236,9 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3734aecf9ff79aa401a6ca099d076535ab465ff76b46440cf567c8e70b65dc13" +checksum = "ddfb9021f581b71870a17eac25b52335b82211cdc092e02b6876b2bcefa61666" dependencies = [ "aws-credential-types", "aws-smithy-http", @@ -1272,9 +1289,9 @@ dependencies = [ [[package]] name = "aws-smithy-json" -version = "0.61.3" +version = "0.61.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92144e45819cae7dc62af23eac5a038a58aa544432d2102609654376a900bd07" +checksum = "a16e040799d29c17412943bdbf488fd75db04112d0c0d4b9290bacf5ae0014b9" dependencies = [ "aws-smithy-types", ] @@ -1313,9 +1330,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e5d9e3a80a18afa109391fb5ad09c3daf887b516c6fd805a157c6ea7994a57" +checksum = "bd8531b6d8882fd8f48f82a9754e682e29dd44cff27154af51fa3eb730f59efb" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -1330,9 +1347,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.3.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40076bd09fadbc12d5e026ae080d0930defa606856186e31d83ccc6a255eeaf3" +checksum = "d498595448e43de7f4296b7b7a18a8a02c61ec9349128c80a368f7c3b4ab11a8" dependencies = [ "base64-simd", "bytes", @@ -1502,9 +1519,9 @@ dependencies = [ [[package]] name = "base64ct" -version = "1.7.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "bech32" @@ -1548,9 +1565,9 @@ dependencies = [ [[package]] name = "bip39" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33415e24172c1b7d6066f6d999545375ab8e1d95421d6784bdfff9496f292387" +checksum = "43d193de1f7487df1914d3a568b772458861d33f9c54249612cc2893d6915054" dependencies = [ "bitcoin_hashes 0.13.0", "rand 0.8.5", @@ -1644,9 +1661,9 @@ dependencies = [ [[package]] name = "blst" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c79a94619fade3c0b887670333513a67ac28a6a7e653eb260bf0d4103db38d" +checksum = "4fd49896f12ac9b6dcd7a5998466b9b58263a695a3dd1ecc1aaca2e12a90b080" dependencies = [ "cc", "glob", @@ -1684,7 +1701,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1699,7 +1716,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1722,7 +1739,7 @@ dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1794,15 +1811,15 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.101", + "syn 2.0.104", "zstd", ] [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "byte-slice-cast" @@ -1845,9 +1862,9 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.23.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" [[package]] name = "byteorder" @@ -1891,9 +1908,9 @@ dependencies = [ [[package]] name = "camino" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" dependencies = [ "serde", ] @@ -1940,9 +1957,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.24" +version = "1.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" dependencies = [ "jobserver", "libc", @@ -1957,9 +1974,9 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "cfg_aliases" @@ -2019,14 +2036,14 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "coins-bip32" @@ -2104,9 +2121,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "combine" @@ -2314,9 +2331,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-bigint" @@ -2361,7 +2378,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -2372,7 +2389,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -2412,7 +2429,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 2.0.104", ] [[package]] @@ -2491,7 +2508,7 @@ checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", "unicode-xid", ] @@ -2550,7 +2567,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -2583,6 +2600,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "dyn-clone" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" + [[package]] name = "ecdsa" version = "0.16.9" @@ -2607,7 +2630,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -2672,7 +2695,7 @@ checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -2717,7 +2740,7 @@ checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -2738,12 +2761,12 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -2858,9 +2881,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", "miniz_oxide", @@ -3012,7 +3035,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -3059,9 +3082,9 @@ checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" [[package]] name = "gcloud-sdk" -version = "0.27.1" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44163d7c0d0bc470caa43766beda779e584034abd6b681319d8f9be2fc194eba" +checksum = "a3ec9c312db09dc0dac684dda2f18d76e9ce00effdd27fcaaa90fa811691cd6d" dependencies = [ "async-trait", "bytes", @@ -3105,7 +3128,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -3151,7 +3174,7 @@ dependencies = [ "nonzero_ext", "parking_lot", "portable-atomic", - "quanta 0.12.5", + "quanta 0.12.6", "rand 0.9.1", "smallvec", "spinning_top", @@ -3273,7 +3296,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.9.0", + "indexmap 2.10.0", "slab", "tokio", "tokio-util", @@ -3292,7 +3315,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.3.1", - "indexmap 2.9.0", + "indexmap 2.10.0", "slab", "tokio", "tokio-util", @@ -3325,9 +3348,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.15.3" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" dependencies = [ "allocator-api2", "equivalent", @@ -3341,16 +3364,16 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.15.3", + "hashbrown 0.15.4", ] [[package]] name = "headers" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" +checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "bytes", "headers-core", "http 1.3.1", @@ -3382,15 +3405,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hermit-abi" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hex" @@ -3546,7 +3563,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2 0.5.10", "tokio", "tower-service", "tracing", @@ -3603,15 +3620,15 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.6" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a01595e11bdcec50946522c32dde3fc6914743000a68b93000965f2f02406d" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ "http 1.3.1", "hyper 1.6.0", "hyper-util", "log", - "rustls 0.23.27", + "rustls 0.23.28", "rustls-native-certs", "rustls-pki-types", "tokio", @@ -3663,9 +3680,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8" +checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" dependencies = [ "base64 0.22.1", "bytes", @@ -3841,7 +3858,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -3860,9 +3877,11 @@ version = "0.1.0" dependencies = [ "anyhow", "indexer-allocation", + "sha2", "test-assets", "test-log", "thegraph-core", + "tracing", ] [[package]] @@ -4104,12 +4123,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown 0.15.3", + "hashbrown 0.15.4", "serde", ] @@ -4120,7 +4139,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "232929e1d75fe899576a3d5c7416ad0d88dbfbb3c3d6aa00873a7408a50ddb88" dependencies = [ "ahash 0.8.12", - "indexmap 2.9.0", + "indexmap 2.10.0", "is-terminal", "itoa", "log", @@ -4153,14 +4172,17 @@ name = "integration-tests" version = "0.1.0" dependencies = [ "anyhow", + "base64 0.22.1", "bip39", "clap", "indexer-receipt", "num_cpus", + "prost", "rand 0.9.1", "reqwest 0.12.20", "serde", "serde_json", + "tap_aggregator", "tap_core", "tap_graph", "thegraph-core", @@ -4232,7 +4254,7 @@ version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ - "hermit-abi 0.5.1", + "hermit-abi", "libc", "windows-sys 0.59.0", ] @@ -4399,11 +4421,11 @@ dependencies = [ "base64 0.22.1", "http-body 1.0.1", "hyper 1.6.0", - "hyper-rustls 0.27.6", + "hyper-rustls 0.27.7", "hyper-util", "jsonrpsee-core 0.24.9", "jsonrpsee-types 0.24.9", - "rustls 0.23.27", + "rustls 0.23.28", "rustls-platform-verifier", "serde", "serde_json", @@ -4424,7 +4446,7 @@ dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -4551,9 +4573,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.172" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libm" @@ -4563,9 +4585,9 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" dependencies = [ "bitflags 2.9.1", "libc", @@ -4622,7 +4644,7 @@ checksum = "04d55ca5d5a14363da83bf3c33874b8feaa34653e760d5216d7ef9829c88001a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -4639,9 +4661,9 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -4659,20 +4681,14 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465" dependencies = [ - "hashbrown 0.15.3", + "hashbrown 0.15.4", ] -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - [[package]] name = "mach2" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" dependencies = [ "libc", ] @@ -4685,7 +4701,7 @@ checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -4715,9 +4731,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memmap2" @@ -4770,7 +4786,7 @@ checksum = "38b4faf00617defe497754acde3024865bc143d44a86799b24e191ecff91354f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -4806,9 +4822,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] @@ -4820,7 +4836,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] @@ -5102,44 +5118,45 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi", "libc", ] [[package]] name = "num_enum" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" dependencies = [ "num_enum_derive", + "rustversion", ] [[package]] name = "num_enum_derive" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "nybbles" -version = "0.3.4" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8983bb634df7248924ee0c4c3a749609b5abcb082c28fffe3254b3eb3602b307" +checksum = "11d51b0175c49668a033fe7cc69080110d9833b291566cdf332905f3ad9c68a0" dependencies = [ "alloy-rlp", - "const-hex", "proptest", + "ruint", "serde", "smallvec", ] @@ -5186,9 +5203,9 @@ checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] name = "openssl" -version = "0.10.72" +version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ "bitflags 2.9.1", "cfg-if", @@ -5207,7 +5224,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -5218,9 +5235,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.108" +version = "0.9.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" dependencies = [ "cc", "libc", @@ -5236,7 +5253,7 @@ checksum = "1e32339a5dc40459130b3bd269e9892439f55b33e772d2a9d402a789baaf4e8a" dependencies = [ "futures-core", "futures-sink", - "indexmap 2.9.0", + "indexmap 2.10.0", "js-sys", "once_cell", "pin-project-lite", @@ -5339,7 +5356,7 @@ dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -5350,9 +5367,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -5360,9 +5377,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", @@ -5407,7 +5424,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -5437,9 +5454,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" +checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", "thiserror 2.0.12", @@ -5453,7 +5470,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", - "indexmap 2.9.0", + "indexmap 2.10.0", ] [[package]] @@ -5493,7 +5510,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -5548,9 +5565,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "portable-atomic" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "potential_utf" @@ -5612,12 +5629,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.32" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" +checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" dependencies = [ "proc-macro2", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -5693,7 +5710,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -5713,7 +5730,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", "version_check", "yansi", ] @@ -5780,22 +5797,22 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "proptest" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" +checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" dependencies = [ "bit-set", "bit-vec", "bitflags 2.9.1", "lazy_static", "num-traits", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand 0.9.1", + "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax 0.8.5", "rusty-fork", @@ -5819,7 +5836,7 @@ version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "itertools 0.14.0", "log", "multimap", @@ -5829,7 +5846,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.101", + "syn 2.0.104", "tempfile", ] @@ -5843,7 +5860,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -5931,22 +5948,22 @@ dependencies = [ "mach2", "once_cell", "raw-cpuid 10.7.0", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "web-sys", "winapi", ] [[package]] name = "quanta" -version = "0.12.5" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bd1fe6824cea6538803de3ff1bc0cf3949024db3d43c9643024bfb33a807c0e" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" dependencies = [ "crossbeam-utils", "libc", "once_cell", "raw-cpuid 11.5.0", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "web-sys", "winapi", ] @@ -5966,61 +5983,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "quinn" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" -dependencies = [ - "bytes", - "cfg_aliases 0.2.1", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls 0.23.27", - "socket2 0.5.10", - "thiserror 2.0.12", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" -dependencies = [ - "bytes", - "getrandom 0.3.3", - "lru-slab", - "rand 0.9.1", - "ring", - "rustc-hash", - "rustls 0.23.27", - "rustls-pki-types", - "slab", - "thiserror 2.0.12", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" -dependencies = [ - "cfg_aliases 0.2.1", - "libc", - "once_cell", - "socket2 0.5.10", - "tracing", - "windows-sys 0.59.0", -] - [[package]] name = "quote" version = "1.0.40" @@ -6032,9 +5994,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "ractor" @@ -6122,11 +6084,11 @@ dependencies = [ [[package]] name = "rand_xorshift" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ - "rand_core 0.6.4", + "rand_core 0.9.3", ] [[package]] @@ -6201,9 +6163,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" dependencies = [ "bitflags 2.9.1", ] @@ -6219,6 +6181,26 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "ref-cast" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "regex" version = "1.11.1" @@ -6346,7 +6328,7 @@ dependencies = [ "http-body 1.0.1", "http-body-util", "hyper 1.6.0", - "hyper-rustls 0.27.6", + "hyper-rustls 0.27.7", "hyper-tls 0.6.0", "hyper-util", "js-sys", @@ -6356,8 +6338,7 @@ dependencies = [ "native-tls", "percent-encoding", "pin-project-lite", - "quinn", - "rustls 0.23.27", + "rustls 0.23.28", "rustls-native-certs", "rustls-pki-types", "serde", @@ -6511,7 +6492,7 @@ dependencies = [ "regex", "relative-path", "rustc_version 0.4.1", - "syn 2.0.101", + "syn 2.0.104", "unicode-ident", ] @@ -6560,9 +6541,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.37.1" +version = "1.37.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa7de2ba56ac291bd90c6b9bece784a52ae1411f9506544b3eae36dd2356d50" +checksum = "b203a6425500a03e0919c42d3c47caca51e79f1132046626d2c8871c5092035d" dependencies = [ "arrayvec 0.7.6", "borsh", @@ -6576,9 +6557,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] name = "rustc-hash" @@ -6637,9 +6618,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.27" +version = "0.23.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" +checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" dependencies = [ "log", "once_cell", @@ -6677,7 +6658,6 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ - "web-time", "zeroize", ] @@ -6692,7 +6672,7 @@ dependencies = [ "jni", "log", "once_cell", - "rustls 0.23.27", + "rustls 0.23.28", "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki 0.103.3", @@ -6804,6 +6784,18 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -6851,7 +6843,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77253fb2d4451418d07025826028bcb96ee42d3e58859689a70ce62908009db6" dependencies = [ "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -7004,7 +6996,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -7046,7 +7038,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -7081,15 +7073,16 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +checksum = "bf65a400f8f66fb7b0552869ad70157166676db75ed8181f8104ea91cf9d0b42" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.9.0", + "indexmap 2.10.0", + "schemars", "serde", "serde_derive", "serde_json", @@ -7099,14 +7092,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +checksum = "81679d9ed988d5e9a5e6531dc3f2c28efbd639cbd1dfb628df08edea6004da77" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -7115,7 +7108,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.9.0", + "indexmap 2.10.0", "itoa", "ryu", "serde", @@ -7154,7 +7147,7 @@ checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -7210,12 +7203,13 @@ dependencies = [ [[package]] name = "shared_child" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e297bd52991bbe0686c086957bee142f13df85d1e79b0b21630a99d374ae9dc" +checksum = "c2778001df1384cf20b6dc5a5a90f48da35539885edaaefd887f8d744e939c0b" dependencies = [ "libc", - "windows-sys 0.59.0", + "sigchld", + "windows-sys 0.60.2", ] [[package]] @@ -7224,6 +7218,27 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "sigchld" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1219ef50fc0fdb04fcc243e6aa27f855553434ffafe4fa26554efb78b5b4bf89" +dependencies = [ + "libc", + "os_pipe", + "signal-hook", +] + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[package]] name = "signal-hook-registry" version = "1.4.5" @@ -7275,18 +7290,15 @@ checksum = "85636c14b73d81f541e525f585c0a2109e6744e1565b5c1668e31c70c10ed65c" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" dependencies = [ "serde", ] @@ -7335,7 +7347,7 @@ checksum = "c87e960f4dca2788eeb86bbdde8dd246be8948790b7618d656e68f9b720a86e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -7397,9 +7409,9 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.15.3", + "hashbrown 0.15.4", "hashlink", - "indexmap 2.9.0", + "indexmap 2.10.0", "log", "memchr", "once_cell", @@ -7427,7 +7439,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -7450,7 +7462,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.101", + "syn 2.0.104", "tokio", "url", ] @@ -7644,7 +7656,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -7657,7 +7669,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -7701,9 +7713,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.101" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -7712,14 +7724,14 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "1.1.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d879005cc1b5ba4e18665be9e9501d9da3a9b95f625497c4cb7ee082b532e" +checksum = "b9ac494e7266fcdd2ad80bf4375d55d27a117ea5c866c26d0e97fe5b3caeeb75" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -7757,14 +7769,14 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "sysinfo" -version = "0.35.1" +version = "0.35.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79251336d17c72d9762b8b54be4befe38d2db56fbbc0241396d70f173c39d47a" +checksum = "3c3ffa3e4ff2b324a57f7aeb3c349656c7b127c3c189520251a648102a92496e" dependencies = [ "libc", "memchr", @@ -7824,9 +7836,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tap_aggregator" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396a205d0b37082e143bacb0df2863f012efdb61030c761e4769e565a3682cac" +checksum = "a285f761794c1f27f3d744cea3adb9860b625a34f0cc4f0637128b6a2c9611ea" dependencies = [ "anyhow", "axum", @@ -7883,9 +7895,9 @@ dependencies = [ [[package]] name = "tap_graph" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef92cf034d1da570ad6c2fbb949ef6c481e973854b4123c7ae2cb6f4c35a6a8b" +checksum = "ff0f8dd640afe0d3a53a6b6ae58f628f0f99267d33f61b7a1264daa9d4307003" dependencies = [ "rand 0.9.1", "serde", @@ -7940,9 +7952,9 @@ dependencies = [ [[package]] name = "test-log" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f46083d221181166e5b6f6b1e5f1d499f3a76888826e6cb1d057554157cd0f" +checksum = "1e33b98a582ea0be1168eba097538ee8dd4bbe0f2b01b22ac92ea30054e5be7b" dependencies = [ "test-log-macros", "tracing-subscriber", @@ -7950,20 +7962,20 @@ dependencies = [ [[package]] name = "test-log-macros" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "888d0c3c6db53c0fdab160d2ed5e12ba745383d3e85813f2ea0f2b1475ab553f" +checksum = "451b374529930d7601b1eef8d32bc79ae870b6079b069401709c2a8bf9e75f36" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "test-with" -version = "0.14.10" +version = "0.14.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ede0159d14173a6362219475c776e889bd35f486afdfeaf8a000e72947d12f" +checksum = "d75791778b593ea6d76454886a69cd37245c4c9af6fee37c42f024593bf1fb03" dependencies = [ "byte-unit", "chrono", @@ -7974,7 +7986,7 @@ dependencies = [ "quote", "regex", "reqwest 0.12.20", - "syn 2.0.101", + "syn 2.0.104", "sysinfo", "uzers", "which", @@ -8032,7 +8044,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -8043,17 +8055,16 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] @@ -8165,7 +8176,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -8194,7 +8205,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ - "rustls 0.23.27", + "rustls 0.23.28", "tokio", ] @@ -8285,7 +8296,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.9.0", + "indexmap 2.10.0", "serde", "serde_spanned", "toml_datetime", @@ -8343,7 +8354,7 @@ dependencies = [ "prost-build", "prost-types", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -8369,7 +8380,7 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", - "indexmap 2.9.0", + "indexmap 2.10.0", "pin-project-lite", "slab", "sync_wrapper 1.0.2", @@ -8469,20 +8480,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -8560,7 +8571,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" dependencies = [ "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -8651,9 +8662,9 @@ checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] @@ -8817,9 +8828,9 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" @@ -8858,7 +8869,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", "wasm-bindgen-shared", ] @@ -8893,7 +8904,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -8922,9 +8933,9 @@ dependencies = [ [[package]] name = "wasmtimer" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0048ad49a55b9deb3953841fa1fc5858f0efbcb7a18868c899a360269fac1b23" +checksum = "d8d49b5d6c64e8558d9b1b065014426f35c18de636895d24893dbbd329743446" dependencies = [ "futures", "js-sys", @@ -8960,14 +8971,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" dependencies = [ - "webpki-root-certs 1.0.0", + "webpki-root-certs 1.0.1", ] [[package]] name = "webpki-root-certs" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a83f7e1a9f8712695c03eabe9ed3fbca0feff0152f33f12593e5a6303cb1a4" +checksum = "86138b15b2b7d561bc4469e77027b8dd005a43dc502e9031d1f5afc8ce1f280e" dependencies = [ "rustls-pki-types", ] @@ -8980,11 +8991,10 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "which" -version = "7.0.3" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762" +checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d" dependencies = [ - "either", "env_home", "rustix", "winsafe", @@ -9022,7 +9032,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -9033,9 +9043,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.61.1" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ "windows-collections", "windows-core", @@ -9063,7 +9073,7 @@ dependencies = [ "windows-interface", "windows-link", "windows-result", - "windows-strings 0.4.2", + "windows-strings", ] [[package]] @@ -9085,7 +9095,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -9096,14 +9106,14 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "windows-link" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-numerics" @@ -9117,13 +9127,13 @@ dependencies = [ [[package]] name = "windows-registry" -version = "0.4.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ + "windows-link", "windows-result", - "windows-strings 0.3.1", - "windows-targets 0.53.0", + "windows-strings", ] [[package]] @@ -9135,15 +9145,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-strings" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-strings" version = "0.4.2" @@ -9189,6 +9190,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -9237,9 +9247,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.0" +version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" dependencies = [ "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", @@ -9442,9 +9452,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" dependencies = [ "memchr", ] @@ -9554,28 +9564,28 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", "synstructure 0.13.2", ] [[package]] name = "zerocopy" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -9595,7 +9605,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", "synstructure 0.13.2", ] @@ -9616,7 +9626,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -9649,7 +9659,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 5c07c72f5..faf3dc7d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,6 +70,7 @@ serde = { version = "1.0.206", default-features = false } serde_json = "1.0.124" serde_yaml = "0.9.21" serial_test = "3.2.0" +sha2 = "0.10" sqlx = { version = "0.8.2", features = [ "bigdecimal", "chrono", @@ -82,13 +83,13 @@ sqlx = { version = "0.8.2", features = [ "uuid", ], default-features = false } stdext = "0.3.3" -tap_aggregator = { version = "0.5.5", default-features = false } -tap_core = { version = "4.1.3", default-features = false } -tap_graph = { version = "0.3.3", features = ["v2"] } +tap_aggregator = { version = "0.5.6", default-features = false } +tap_core = { version = "4.1.4", default-features = false } +tap_graph = { version = "0.3.4", features = ["v2"] } tempfile = "3.8.0" test-log = { version = "0.2.12", default-features = false } test-with = "0.14.6" -thegraph-core = { version = "0.15.0", features = [ +thegraph-core = { version = "0.15.1", features = [ "attestation", "alloy-eip712", "alloy-sol-types", diff --git a/contrib/docker-compose.dev.yml b/contrib/docker-compose.dev.yml index 05de611d4..a836590d1 100644 --- a/contrib/docker-compose.dev.yml +++ b/contrib/docker-compose.dev.yml @@ -6,8 +6,8 @@ services: - ../target/release/indexer-service-rs:/usr/local/bin/indexer-service-rs - ./indexer-service/start.sh:/usr/local/bin/start.sh - ./indexer-service/config.toml:/opt/config/config.toml - - ../local-network/contracts.json:/opt/contracts.json:ro - - ../local-network/.env:/opt/.env:ro + - ./local-network/tap-contracts.json:/opt/contracts.json:ro + - ./local-network/.env:/opt/.env:ro - ../migrations:/opt/migrations:ro entrypoint: ["/usr/local/bin/start.sh"] environment: @@ -34,7 +34,7 @@ services: - ../target/release/indexer-tap-agent:/usr/local/bin/indexer-tap-agent - ./tap-agent:/opt/config:ro - ./local-network/.env:/opt/.env:ro - - ./local-network/contracts.json:/opt/contracts.json:ro + - ./local-network/tap-contracts.json:/opt/contracts.json:ro - ../migrations:/opt/migrations:ro entrypoint: ["/bin/bash", "-c", "/opt/config/start.sh"] environment: diff --git a/contrib/docker-compose.yml b/contrib/docker-compose.yml index f7e90057a..b0567c0fd 100644 --- a/contrib/docker-compose.yml +++ b/contrib/docker-compose.yml @@ -6,7 +6,7 @@ services: container_name: indexer-service volumes: - ./indexer-service:/opt/config:ro # From contrib dir to indexer-service dir - - ./local-network/contracts.json:/opt/contracts.json:ro + - ./local-network/tap-contracts.json:/opt/contracts.json:ro - ./local-network/.env:/opt/.env:ro - ../migrations:/opt/migrations:ro entrypoint: ["/bin/bash", "-c", "/opt/config/start.sh"] @@ -35,7 +35,7 @@ services: volumes: - ./tap-agent:/opt/config:ro # From contrib dir to tap-agent dir - ./local-network/.env:/opt/.env:ro - - ./local-network/contracts.json:/opt/contracts.json:ro + - ./local-network/tap-contracts.json:/opt/contracts.json:ro - ../migrations:/opt/migrations:ro entrypoint: ["/bin/bash", "-c", "/opt/config/start.sh"] environment: diff --git a/contrib/indexer-service/config.toml b/contrib/indexer-service/config.toml index ca4b45138..39976ca1f 100644 --- a/contrib/indexer-service/config.toml +++ b/contrib/indexer-service/config.toml @@ -11,11 +11,15 @@ status_url = "http://graph-node:8030/graphql" [subgraphs.network] query_url = "http://graph-node:8000/subgraphs/name/graph-network" -deployment_id = "NETWORK_DEPLOYMENT_PLACEHOLDER" +syncing_interval_secs = 5 +recently_closed_allocation_buffer_secs = 10 [subgraphs.escrow] query_url = "http://graph-node:8000/subgraphs/name/semiotic/tap" deployment_id = "ESCROW_DEPLOYMENT_PLACEHOLDER" +syncing_interval_secs = 5 + +# Note: V2 escrow accounts are in the network subgraph, not a separate TAP v2 subgraph [blockchain] chain_id = 1337 @@ -46,4 +50,9 @@ trigger_value_divisor = 500_000 "ACCOUNT0_ADDRESS_PLACEHOLDER" = "http://tap-aggregator:7610" [horizon] -enabled = false +# Enable Horizon migration support and detection +# When enabled: Check if Horizon contracts are active in the network +# - If Horizon contracts detected: Hybrid migration mode (new V2 receipts only, process existing V1 receipts) +# - If Horizon contracts not detected: Remain in legacy mode (V1 receipts only) +# When disabled: Pure legacy mode, no Horizon detection performed +enabled = true diff --git a/contrib/indexer-service/start.sh b/contrib/indexer-service/start.sh index 2f82d4ee6..d728dd8ce 100755 --- a/contrib/indexer-service/start.sh +++ b/contrib/indexer-service/start.sh @@ -8,21 +8,37 @@ fi cat /opt/.env # Extract TAPVerifier address from contracts.json +stdbuf -oL echo "🔍 DEBUG: Extracting TAPVerifier address from contracts.json..." VERIFIER_ADDRESS=$(jq -r '."1337".TAPVerifier.address' /opt/contracts.json) +stdbuf -oL echo "🔍 DEBUG: TAPVerifier address: $VERIFIER_ADDRESS" # Override with test values taken from test-assets/src/lib.rs ALLOCATION_ID="0xfa44c72b753a66591f241c7dc04e8178c30e13af" # ALLOCATION_ID_0 # Get network subgraph deployment ID -NETWORK_DEPLOYMENT=$(curl -s "http://graph-node:8000/subgraphs/name/graph-network" \ +stdbuf -oL echo "🔍 DEBUG: Fetching network subgraph deployment ID..." +NETWORK_DEPLOYMENT=$(curl -s --max-time 10 "http://graph-node:8000/subgraphs/name/graph-network" \ -H 'content-type: application/json' \ -d '{"query": "{ _meta { deployment } }"}' | jq -r '.data._meta.deployment' 2>/dev/null) +stdbuf -oL echo "🔍 DEBUG: Network deployment result: $NETWORK_DEPLOYMENT" stdbuf -oL echo "Graph-network subgraph deployment ID: $NETWORK_DEPLOYMENT" # Get escrow subgraph deployment ID -ESCROW_DEPLOYMENT=$(curl -s "http://graph-node:8000/subgraphs/name/semiotic/tap" \ +stdbuf -oL echo "🔍 DEBUG: Fetching escrow subgraph deployment ID..." +ESCROW_DEPLOYMENT=$(curl -s --max-time 10 "http://graph-node:8000/subgraphs/name/semiotic/tap" \ -H 'content-type: application/json' \ -d '{"query": "{ _meta { deployment } }"}' | jq -r '.data._meta.deployment' 2>/dev/null) +stdbuf -oL echo "🔍 DEBUG: Escrow deployment result: $ESCROW_DEPLOYMENT" + +# Handle null deployment IDs by removing the lines entirely +if [ "$NETWORK_DEPLOYMENT" = "null" ] || [ -z "$NETWORK_DEPLOYMENT" ]; then + NETWORK_DEPLOYMENT="" +fi + +if [ "$ESCROW_DEPLOYMENT" = "null" ] || [ -z "$ESCROW_DEPLOYMENT" ]; then + ESCROW_DEPLOYMENT="" +fi + stdbuf -oL echo "Escrow subgraph deployment ID: $ESCROW_DEPLOYMENT" stdbuf -oL echo "Using test Network subgraph deployment ID: $NETWORK_DEPLOYMENT" @@ -35,8 +51,20 @@ stdbuf -oL echo "Using test Account0 address: $ACCOUNT0_ADDRESS" cp /opt/config/config.toml /opt/config.toml # Replace the placeholders with actual values -sed -i "s/NETWORK_DEPLOYMENT_PLACEHOLDER/$NETWORK_DEPLOYMENT/g" /opt/config.toml -sed -i "s/ESCROW_DEPLOYMENT_PLACEHOLDER/$ESCROW_DEPLOYMENT/g" /opt/config.toml +if [ -n "$NETWORK_DEPLOYMENT" ]; then + sed -i "s/NETWORK_DEPLOYMENT_PLACEHOLDER/$NETWORK_DEPLOYMENT/g" /opt/config.toml +else + # Remove the deployment_id line entirely for network subgraph + sed -i '/deployment_id = "NETWORK_DEPLOYMENT_PLACEHOLDER"/d' /opt/config.toml +fi + +if [ -n "$ESCROW_DEPLOYMENT" ]; then + sed -i "s/ESCROW_DEPLOYMENT_PLACEHOLDER/$ESCROW_DEPLOYMENT/g" /opt/config.toml +else + # Remove the deployment_id line entirely for escrow subgraph + sed -i '/deployment_id = "ESCROW_DEPLOYMENT_PLACEHOLDER"/d' /opt/config.toml +fi + sed -i "s/VERIFIER_ADDRESS_PLACEHOLDER/$VERIFIER_ADDRESS/g" /opt/config.toml sed -i "s/INDEXER_ADDRESS_PLACEHOLDER/$RECEIVER_ADDRESS/g" /opt/config.toml sed -i "s/INDEXER_MNEMONIC_PLACEHOLDER/$INDEXER_MNEMONIC/g" /opt/config.toml diff --git a/contrib/tap-agent/config.toml b/contrib/tap-agent/config.toml index ca4b45138..bed7bc21a 100644 --- a/contrib/tap-agent/config.toml +++ b/contrib/tap-agent/config.toml @@ -12,10 +12,15 @@ status_url = "http://graph-node:8030/graphql" [subgraphs.network] query_url = "http://graph-node:8000/subgraphs/name/graph-network" deployment_id = "NETWORK_DEPLOYMENT_PLACEHOLDER" +syncing_interval_secs = 5 +recently_closed_allocation_buffer_secs = 10 [subgraphs.escrow] query_url = "http://graph-node:8000/subgraphs/name/semiotic/tap" deployment_id = "ESCROW_DEPLOYMENT_PLACEHOLDER" +syncing_interval_secs = 5 + +# Note: V2 escrow accounts are in the network subgraph, not a separate TAP v2 subgraph [blockchain] chain_id = 1337 @@ -46,4 +51,9 @@ trigger_value_divisor = 500_000 "ACCOUNT0_ADDRESS_PLACEHOLDER" = "http://tap-aggregator:7610" [horizon] -enabled = false +# Enable Horizon migration support and detection +# When enabled: Check if Horizon contracts are active in the network +# - If Horizon contracts detected: Hybrid migration mode (new V2 receipts only, process existing V1 receipts) +# - If Horizon contracts not detected: Remain in legacy mode (V1 receipts only) +# When disabled: Pure legacy mode, no Horizon detection performed +enabled = true diff --git a/contrib/tap-agent/start.sh b/contrib/tap-agent/start.sh index 245045a84..6641a2be7 100755 --- a/contrib/tap-agent/start.sh +++ b/contrib/tap-agent/start.sh @@ -105,6 +105,7 @@ fi echo "Escrow subgraph deployment ID: $ESCROW_DEPLOYMENT" + # Copy the config template cp /opt/config/config.toml /opt/config.toml diff --git a/crates/attestation/Cargo.toml b/crates/attestation/Cargo.toml index 4eb71bee8..8c1fdfe60 100644 --- a/crates/attestation/Cargo.toml +++ b/crates/attestation/Cargo.toml @@ -6,7 +6,9 @@ edition = "2021" [dependencies] anyhow.workspace = true indexer-allocation = { path = "../allocation" } +sha2.workspace = true thegraph-core.workspace = true +tracing.workspace = true [dev-dependencies] diff --git a/crates/attestation/src/lib.rs b/crates/attestation/src/lib.rs index f761109a8..4bfdaf431 100644 --- a/crates/attestation/src/lib.rs +++ b/crates/attestation/src/lib.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use indexer_allocation::Allocation; +use sha2::{Digest, Sha256}; use thegraph_core::{ alloy::{ primitives::{Address, ChainId}, @@ -21,6 +22,30 @@ pub fn derive_key_pair( epoch: u64, deployment: &DeploymentId, index: u64, +) -> Result { + // Try the original method first for backward compatibility + match derive_key_pair_v1(indexer_mnemonic, epoch, deployment, index) { + Ok(wallet) => Ok(wallet), + Err(e) => { + // If the original method fails (likely due to path length), try v2 + tracing::debug!( + "V1 derivation failed for epoch={}, deployment={}, index={}: {}. Trying V2.", + epoch, + deployment, + index, + e + ); + derive_key_pair_v2(indexer_mnemonic, epoch, deployment, index) + } + } +} + +/// Original derivation method - kept for backward compatibility +fn derive_key_pair_v1( + indexer_mnemonic: &str, + epoch: u64, + deployment: &DeploymentId, + index: u64, ) -> Result { let mut derivation_path = format!("m/{epoch}/"); derivation_path.push_str( @@ -28,11 +53,61 @@ pub fn derive_key_pair( .to_string() .as_bytes() .iter() - .map(|char| char.to_string()) + .map(|byte| byte.to_string()) .collect::>() .join("/"), ); - derivation_path.push_str(format!("/{index}").as_str()); + derivation_path.push_str(&format!("/{index}")); + + // Check path length before attempting derivation + // Use a more generous limit - most BIP32 implementations can handle longer paths + // The original issue was likely with extremely long paths (200+ chars) + if derivation_path.len() > 200 { + return Err(anyhow::anyhow!( + "BIP32 path too long: {} characters, deployment: {}", + derivation_path.len(), + deployment + )); + } + + Ok(MnemonicBuilder::::default() + .derivation_path(&derivation_path) + .expect("Valid derivation path") + .phrase(indexer_mnemonic) + .build()?) +} + +/// V2 derivation method - uses hash of deployment to create shorter, deterministic paths +fn derive_key_pair_v2( + indexer_mnemonic: &str, + epoch: u64, + deployment: &DeploymentId, + index: u64, +) -> Result { + // Hash the deployment ID to create a fixed-size representation + let mut hasher = Sha256::new(); + hasher.update(deployment.to_string().as_bytes()); + let deployment_hash = hasher.finalize(); + + // Convert hash to u32 values for BIP32 path components (within valid range) + let hash_bytes: [u8; 32] = deployment_hash.into(); + let deployment_part1 = + u32::from_be_bytes([hash_bytes[0], hash_bytes[1], hash_bytes[2], hash_bytes[3]]) + & 0x7FFFFFFF; // Ensure < 2^31 for BIP32 compatibility + let deployment_part2 = + u32::from_be_bytes([hash_bytes[4], hash_bytes[5], hash_bytes[6], hash_bytes[7]]) + & 0x7FFFFFFF; + + // Build a compact BIP32 path using hardened derivation + let derivation_path = format!("m/{epoch}'/{deployment_part1}'/{deployment_part2}'/{index}'"); + + tracing::debug!( + "V2 derivation: epoch={}, deployment={}, index={}, path={}", + epoch, + deployment, + index, + derivation_path + ); Ok(MnemonicBuilder::::default() .derivation_path(&derivation_path) @@ -92,6 +167,13 @@ fn wallet_for_allocation( indexer_mnemonic: &str, allocation: &Allocation, ) -> Result { + tracing::debug!( + "Starting wallet derivation for allocation {}, deployment {}, epoch {}", + allocation.id, + allocation.subgraph_deployment.id, + allocation.created_at_epoch + ); + // Guess the allocation index by enumerating all indexes in the // range [0, 100] and checking for a match for i in 0..100 { @@ -107,14 +189,71 @@ fn wallet_for_allocation( i, )?; - // See if we have a match, i.e. a wallet whose address is identical to the allocation ID - if wallet.address() == allocation.id { + let wallet_address = wallet.address(); + + if i < 5 || (i % 20 == 0) { + // Log first 5 attempts and every 20th attempt + tracing::debug!( + "Derivation attempt: epoch={}, index={}, derived_address={}, target_allocation={}", + created_at_epoch, i, wallet_address, allocation.id + ); + } + + // Check if wallet address matches allocation ID + // This works for both Legacy (V1) and Horizon (V2) as both use 20-byte allocation IDs + if wallet_address == allocation.id { + tracing::debug!( + "Found matching wallet! epoch={}, index={}, address={}", + created_at_epoch, + i, + wallet_address + ); return Ok(wallet); } } } + + // Enhanced error reporting for troubleshooting + tracing::warn!( + "Cannot derive attestation signer for allocation {} after trying 200 key combinations. \ + The reason is unclear - this could be a configuration issue, but we're not certain.", + allocation.id + ); + + tracing::debug!( + "What we tried: allocation_id={}, deployment={}, created_at_epoch={}, \ + tested epochs {} and {}, tested indices 0-99 for each epoch", + allocation.id, + allocation.subgraph_deployment.id, + allocation.created_at_epoch, + allocation.created_at_epoch, + allocation.created_at_epoch - 1 + ); + + // Show what we actually derived to help with troubleshooting + tracing::debug!("Here's what our key derivation produced instead:"); + for &epoch in [allocation.created_at_epoch, allocation.created_at_epoch - 1].iter() { + for i in 0..3 { + if let Ok(wallet) = derive_key_pair( + indexer_mnemonic, + epoch, + &allocation.subgraph_deployment.id, + i, + ) { + tracing::debug!( + " epoch={}, index={}, we_derived={}", + epoch, + i, + wallet.address() + ); + } + } + } + Err(anyhow::anyhow!( - "Could not generate wallet matching allocation {}", + "No key combination we tried matched allocation {}. \ + We tested 200 different combinations but none produced this allocation ID. \ + Check the debug logs above to see what we actually derived.", allocation.id )) } @@ -138,6 +277,61 @@ mod tests { const INDEXER_OPERATOR_MNEMONIC: &str = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; + #[test] + fn test_deployment_bytes_analysis() { + let deployment = DeploymentId::from_str( + "0xbbde25a2c85f55b53b7698b9476610c3d1202d88870e66502ab0076b7218f98a", + ) + .unwrap(); + + println!("Deployment string: {deployment}"); + println!( + "Deployment as_bytes length: {}", + deployment.as_bytes().len() + ); + println!( + "Deployment string bytes length: {}", + deployment.to_string().len() + ); + + // Build the exact path with m/ prefix like our implementation + let epoch = 953; + let index = 0; + let mut derivation_path = format!("m/{epoch}/"); + derivation_path.push_str( + &deployment + .to_string() + .as_bytes() + .iter() + .map(|byte| byte.to_string()) + .collect::>() + .join("/"), + ); + derivation_path.push_str(&format!("/{index}")); + + println!("Rust derivation path: {derivation_path}"); + println!("Path length: {}", derivation_path.len()); + + // Check if it's going to fallback + println!( + "Will trigger fallback (>120): {}", + derivation_path.len() > 120 + ); + + // Test both v1 and v2 derivations for this deployment + println!("\n--- Testing V1 derivation ---"); + match derive_key_pair_v1(INDEXER_OPERATOR_MNEMONIC, epoch, &deployment, index) { + Ok(wallet) => println!("V1 result: 0x{:x}", wallet.address()), + Err(e) => println!("V1 failed: {e}"), + } + + println!("\n--- Testing V2 derivation ---"); + match derive_key_pair_v2(INDEXER_OPERATOR_MNEMONIC, epoch, &deployment, index) { + Ok(wallet) => println!("V2 result: 0x{:x}", wallet.address()), + Err(e) => println!("V2 failed: {e}"), + } + } + #[test] fn test_derive_key_pair() { assert_eq!( @@ -217,6 +411,202 @@ mod tests { ); } + #[test] + fn test_v2_fallback_for_long_paths() { + // Test the problematic deployment from the original error logs + let problematic_deployment = "QmcBcJvmRyCfwgGbU5hmJbnQfb3hJxQtY3Hxp4AbYuymLf"; + + let deployment = DeploymentId::from_str(problematic_deployment).unwrap(); + let epoch = 123; // Example epoch + let index = 0; + + // Check the actual path length for this deployment + let mut derivation_path = format!("m/{epoch}/"); + derivation_path.push_str( + &deployment + .to_string() + .as_bytes() + .iter() + .map(|byte| byte.to_string()) + .collect::>() + .join("/"), + ); + derivation_path.push_str(&format!("/{index}")); + + println!("Testing deployment: {deployment}"); + println!("Path length: {}", derivation_path.len()); + println!("Exceeds 200 chars: {}", derivation_path.len() > 200); + + // Test V1 - should work if path <= 200 chars + let v1_result = derive_key_pair_v1(INDEXER_OPERATOR_MNEMONIC, epoch, &deployment, index); + match &v1_result { + Ok(wallet) => println!("V1 succeeded: 0x{:x}", wallet.address()), + Err(e) => println!("V1 failed: {e}"), + } + + // Test V2 + let v2_result = + derive_key_pair_v2(INDEXER_OPERATOR_MNEMONIC, epoch, &deployment, index).unwrap(); + println!("V2 result: 0x{:x}", v2_result.address()); + + // Test the main function + let main_result = + derive_key_pair(INDEXER_OPERATOR_MNEMONIC, epoch, &deployment, index).unwrap(); + println!("Main function result: 0x{:x}", main_result.address()); + + // Main function should use V1 if it works, otherwise V2 + if v1_result.is_ok() { + assert_eq!(v1_result.unwrap().address(), main_result.address()); + println!("✓ Main function correctly used V1 for this deployment"); + } else { + assert_eq!(v2_result.address(), main_result.address()); + println!("✓ Main function correctly fell back to V2 for this deployment"); + } + + // Now test with an artificially long deployment that definitely triggers V2 + // Create a deployment string that will exceed 200 chars + let very_long_deployment = format!("Qm{}", "x".repeat(200)); // This will be way too long + if let Ok(long_deployment) = DeploymentId::from_str(&very_long_deployment) { + println!("\n--- Testing artificially long deployment ---"); + match derive_key_pair_v1(INDEXER_OPERATOR_MNEMONIC, epoch, &long_deployment, index) { + Ok(_) => panic!("V1 should have failed for artificially long deployment"), + Err(e) => println!("V1 correctly failed for long deployment: {e}"), + } + + let long_v2_result = + derive_key_pair_v2(INDEXER_OPERATOR_MNEMONIC, epoch, &long_deployment, index) + .unwrap(); + let long_main_result = + derive_key_pair(INDEXER_OPERATOR_MNEMONIC, epoch, &long_deployment, index).unwrap(); + assert_eq!(long_v2_result.address(), long_main_result.address()); + println!("✓ V2 fallback working for artificially long deployment"); + } + } + + #[test] + fn test_specific_failing_allocation() { + // Test the specific allocation that's failing in the logs + let deployment = + DeploymentId::from_str("QmcBcJvmRyCfwgGbU5hmJbnQfb3hJxQtY3Hxp4AbYuymLf").unwrap(); + let target_allocation = address!("AEa0CA4850810AEC59d7C0BD624d6F7766cBC865"); + let epochs_to_try = [3, 2, 1, 4, 5]; // epoch 3 from logs, and nearby epochs + + println!("=== Debugging Specific Failing Allocation ==="); + println!("Target allocation: 0x{target_allocation:x}"); + println!("Deployment: {deployment}"); + println!("Deployment string length: {}", deployment.to_string().len()); + + // Try with the actual local environment mnemonic + let local_mnemonic = "test test test test test test test test test test test zero"; + println!("\n--- Trying with local environment mnemonic ---"); + for &epoch in &epochs_to_try { + for index in 0..20 { + match derive_key_pair(local_mnemonic, epoch, &deployment, index) { + Ok(wallet) => { + let address = wallet.address(); + println!("Epoch: {epoch}, Index: {index}, Address: 0x{address:x}"); + if address == target_allocation { + println!( + "🎯 MATCH FOUND with local mnemonic! Epoch: {epoch}, Index: {index}" + ); + return; + } + } + Err(e) => { + println!("Epoch: {epoch}, Index: {index}, Error: {e}"); + } + } + } + } + + // Try with the indexer operator mnemonic + println!("\n--- Trying with indexer operator mnemonic ---"); + for &epoch in &epochs_to_try { + for index in 0..20 { + match derive_key_pair(INDEXER_OPERATOR_MNEMONIC, epoch, &deployment, index) { + Ok(wallet) => { + let address = wallet.address(); + println!("Epoch: {epoch}, Index: {index}, Address: 0x{address:x}"); + if address == target_allocation { + println!( + "🎯 MATCH FOUND with operator mnemonic! Epoch: {epoch}, Index: {index}" + ); + return; + } + } + Err(e) => { + println!("Epoch: {epoch}, Index: {index}, Error: {e}"); + } + } + } + } + + println!("\n❌ No match found with either mnemonic for the failing allocation"); + println!("This suggests the allocation was created with different parameters or mnemonic"); + } + + #[test] + fn test_debug_allocation_derivation() { + let mnemonic = "test test test test test test test test test test test zero"; + let deployment_hex = "0xbbde25a2c85f55b53b7698b9476610c3d1202d88870e66502ab0076b7218f98a"; + let deployment = DeploymentId::from_str(deployment_hex).unwrap(); + + println!("Testing allocation key derivation with indexer mnemonic:"); + println!("Mnemonic: {mnemonic}"); + println!("Deployment: {deployment}"); + println!(); + + // Test different epochs and indices to find the target allocation ID + for epoch in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] { + for index in 0..10 { + match derive_key_pair(mnemonic, epoch, &deployment, index) { + Ok(wallet) => { + let address = wallet.address(); + println!("Epoch: {epoch}, Index: {index}, Address: 0x{address:x}"); + + // Check if this matches the allocation ID we're looking for + let addr_str = format!("0x{address:x}").to_lowercase(); + if addr_str == "0xec972f0480096adc48b0a355fa9844aa62af26e9" { + println!( + "*** MATCH FOUND! This is the allocation ID we're looking for ***" + ); + } + } + Err(e) => { + println!("Epoch: {epoch}, Index: {index}, Error: {e}"); + } + } + } + println!(); + } + + // Try with the deployment from the error logs + if let Ok(test_deployment) = + DeploymentId::from_str("QmcBcJvmRyCfwgGbU5hmJbnQfb3hJxQtY3Hxp4AbYuymLf") + { + println!("Testing with deployment from logs: {test_deployment}"); + + for epoch in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] { + for index in 0..10 { + match derive_key_pair(mnemonic, epoch, &test_deployment, index) { + Ok(wallet) => { + let address = wallet.address(); + println!("Epoch: {epoch}, Index: {index}, Address: 0x{address:x}"); + + let addr_str = format!("0x{address:x}").to_lowercase(); + if addr_str == "0xec972f0480096adc48b0a355fa9844aa62af26e9" { + println!("*** MATCH FOUND! This is the allocation ID we're looking for ***"); + } + } + Err(e) => { + println!("Epoch: {epoch}, Index: {index}, Error: {e}"); + } + } + } + } + } + } + #[test] fn test_attestation_signer_error() { // Note that because allocation will try 200 derivations paths, this is a slow test diff --git a/crates/config/default_values.toml b/crates/config/default_values.toml index ed414591a..22600115d 100644 --- a/crates/config/default_values.toml +++ b/crates/config/default_values.toml @@ -34,4 +34,4 @@ max_receipts_per_request = 10000 0xDD6a6f76eb36B873C1C184e8b9b9e762FE216490 = "https://tap-aggregator-arbitrum-one.graphops.xyz" [horizon] -enabled = false +enabled = true diff --git a/crates/config/maximal-config-example.toml b/crates/config/maximal-config-example.toml index ba2bfcd2c..e593a3f65 100644 --- a/crates/config/maximal-config-example.toml +++ b/crates/config/maximal-config-example.toml @@ -67,8 +67,9 @@ recently_closed_allocation_buffer_secs = 3600 [subgraphs.escrow] # NOTE: It is heavily recomended to use both `query_url` and `deployment_id`, -# Query URL for the Escrow subgraph. -query_url = "http://example.com/network-subgraph" +# Query URL for the Escrow subgraph (v1). This is the old escrow subgraph. +# NOTE: This is not used in v2, as the escrow subgraph is now in the network subgraph. +query_url = "http://example.com/escrow-subgraph" # Optional, Auth token will used a "bearer auth" # query_auth_token = "super-secret" @@ -78,6 +79,8 @@ deployment_id = "Qmaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" # Refreshing interval for the Escrow contracts information from the Escrow subgraph. syncing_interval_secs = 60 +# NOTE: V2 escrow accounts are now in the network subgraph, not a separate escrow_v2 subgraph + [blockchain] # The chain ID of the network that the graph network is running on chain_id = 1337 @@ -170,4 +173,9 @@ hardhat = "100" "eip155:1337" = "hardhat" [horizon] -enabled = false +# Enable Horizon migration support and detection +# When enabled: Check if Horizon contracts are active in the network +# - If Horizon contracts detected: Hybrid migration mode (new V2 receipts only, process existing V1 receipts) +# - If Horizon contracts not detected: Remain in legacy mode (V1 receipts only) +# When disabled: Pure legacy mode, no Horizon detection performed +enabled = true diff --git a/crates/config/minimal-config-example.toml b/crates/config/minimal-config-example.toml index d26fa5bd2..bfaf97329 100644 --- a/crates/config/minimal-config-example.toml +++ b/crates/config/minimal-config-example.toml @@ -42,8 +42,8 @@ query_url = "http://example.com/network-subgraph" deployment_id = "Qmaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" [subgraphs.escrow] -# Query URL for the Escrow subgraph. -query_url = "http://example.com/network-subgraph" +# Query URL for the Escrow subgraph (v1). +query_url = "http://example.com/escrow-subgraph" # Optional, deployment to look for in the local `graph-node`, if locally indexed. # Locally indexing the subgraph is recommended. # NOTE: Use `query_url` or `deployment_id` only @@ -63,3 +63,11 @@ receipts_verifier_address = "0x2222222222222222222222222222222222222222" # Key-Value of all senders and their aggregator endpoints 0xDDE4cfFd3D9052A9cb618fC05a1Cd02be1f2F467 = "https://tap-aggregator.network.thegraph.com" 0xDD6a6f76eb36B873C1C184e8b9b9e762FE216490 = "https://tap-aggregator-arbitrum-one.graphops.xyz" + +[horizon] +# Enable Horizon migration support and detection +# When enabled: Check if Horizon contracts are active in the network +# - If Horizon contracts detected: Hybrid migration mode (new V2 receipts only, process existing V1 receipts) +# - If Horizon contracts not detected: Remain in legacy mode (V1 receipts only) +# When disabled: Pure legacy mode, no Horizon detection performed +enabled = true diff --git a/crates/config/src/config.rs b/crates/config/src/config.rs index 54e0ce114..36631051c 100644 --- a/crates/config/src/config.rs +++ b/crates/config/src/config.rs @@ -303,6 +303,7 @@ impl MetricsConfig { pub struct SubgraphsConfig { pub network: NetworkSubgraphConfig, pub escrow: EscrowSubgraphConfig, + // Note: V2 escrow accounts are in the network subgraph, not a separate escrow_v2 subgraph } #[serde_as] @@ -446,12 +447,24 @@ pub struct RavRequestConfig { pub max_receipts_per_request: u64, } -/// Configuration for the horizon -/// standard +/// Configuration for the horizon migration #[derive(Debug, Default, Deserialize)] #[cfg_attr(test, derive(PartialEq))] pub struct HorizonConfig { - /// Whether the horizon is enabled or not + /// Enable Horizon migration support and detection + /// + /// When enabled (true): + /// - System will check if Horizon contracts are active in the network + /// - If Horizon contracts are detected: Enable hybrid migration mode + /// * Accept new V2 TAP receipts only + /// * Continue processing existing V1 receipts for RAV generation + /// * Reject new V1 receipt submissions + /// - If Horizon contracts are not detected: Remain in legacy mode + /// + /// When disabled (false): + /// - Pure legacy mode, no Horizon detection performed + /// - Only V1 TAP receipts are supported + #[serde(default)] pub enabled: bool, } diff --git a/crates/indexer-receipt/src/lib.rs b/crates/indexer-receipt/src/lib.rs index c1a79b3b7..cadc24018 100644 --- a/crates/indexer-receipt/src/lib.rs +++ b/crates/indexer-receipt/src/lib.rs @@ -9,7 +9,11 @@ use tap_core::{ }, signed_message::SignatureBytes, }; -use thegraph_core::alloy::{dyn_abi::Eip712Domain, primitives::Address, signers::Signature}; +use thegraph_core::alloy::{ + dyn_abi::Eip712Domain, + primitives::{Address, FixedBytes}, + signers::Signature, +}; #[derive(Debug, Clone, PartialEq, Eq)] pub enum TapReceipt { @@ -70,13 +74,13 @@ impl Aggregate for tap_graph::v2::ReceiptAggregateVoucher { }) .collect::>() .map_err(AggregationError::Other)?; - let allocation_id = receipts[0].message.allocation_id; + let collection_id = receipts[0].message.collection_id; let payer = receipts[0].message.payer; let data_service = receipts[0].message.data_service; let service_provider = receipts[0].message.service_provider; tap_graph::v2::ReceiptAggregateVoucher::aggregate_receipts( - allocation_id, + collection_id, payer, data_service, service_provider, @@ -115,10 +119,17 @@ impl TapReceipt { } } - pub fn allocation_id(&self) -> Address { + pub fn allocation_id(&self) -> Option
{ match self { - TapReceipt::V1(receipt) => receipt.message.allocation_id, - TapReceipt::V2(receipt) => receipt.message.allocation_id, + TapReceipt::V1(receipt) => Some(receipt.message.allocation_id), + _ => None, + } + } + + pub fn collection_id(&self) -> Option> { + match self { + TapReceipt::V2(receipt) => Some(receipt.message.collection_id), + _ => None, } } diff --git a/crates/monitor/src/attestation.rs b/crates/monitor/src/attestation.rs index 2b7fe666b..3adda9aff 100644 --- a/crates/monitor/src/attestation.rs +++ b/crates/monitor/src/attestation.rs @@ -58,10 +58,19 @@ fn modify_sigers( // Create signers for new allocations for (id, allocation) in allocations.iter() { if !signers.contains_key(id) { + tracing::debug!( + "Attempting to create attestation signer for allocation {}, deployment {}, createdAtEpoch {}", + allocation.id, allocation.subgraph_deployment.id, allocation.created_at_epoch + ); + let signer = AttestationSigner::new(indexer_mnemonic, allocation, chain_id, *dispute_manager); match signer { Ok(signer) => { + tracing::debug!( + "Successfully created attestation signer for allocation {}", + allocation.id + ); signers.insert(*id, signer); } Err(e) => { @@ -70,6 +79,11 @@ fn modify_sigers( allocation.id, allocation.subgraph_deployment.id, allocation.created_at_epoch, e ); + tracing::debug!( + "Signer creation error details for allocation {}: {}", + allocation.id, + e + ); } } } diff --git a/crates/monitor/src/escrow_accounts.rs b/crates/monitor/src/escrow_accounts.rs index 167a107fc..e7658da0d 100644 --- a/crates/monitor/src/escrow_accounts.rs +++ b/crates/monitor/src/escrow_accounts.rs @@ -88,6 +88,12 @@ impl EscrowAccounts { pub type EscrowAccountsWatcher = Receiver; +pub fn empty_escrow_accounts_watcher() -> EscrowAccountsWatcher { + let (_, receiver) = + tokio::sync::watch::channel(EscrowAccounts::new(HashMap::new(), HashMap::new())); + receiver +} + pub async fn escrow_accounts_v1( escrow_subgraph: &'static SubgraphClient, indexer_address: Address, @@ -112,13 +118,83 @@ pub async fn escrow_accounts_v2( .await } -// TODO implement escrow accounts v2 query async fn get_escrow_accounts_v2( - _escrow_subgraph: &'static SubgraphClient, - _indexer_address: Address, - _reject_thawing_signers: bool, + escrow_subgraph: &'static SubgraphClient, + indexer_address: Address, + reject_thawing_signers: bool, ) -> anyhow::Result { - Ok(EscrowAccounts::new(HashMap::new(), HashMap::new())) + // Query V2 escrow accounts from the network subgraph which tracks PaymentsEscrow + // and GraphTallyCollector contract events. + + use indexer_query::network_escrow_account_v2::{ + self as network_escrow_account_v2, NetworkEscrowAccountQueryV2, + }; + + let response = escrow_subgraph + .query::(network_escrow_account_v2::Variables { + receiver: format!("{indexer_address:x?}"), + thaw_end_timestamp: if reject_thawing_signers { + U256::ZERO.to_string() + } else { + U256::MAX.to_string() + }, + }) + .await?; + + let response = response?; + + tracing::trace!("Network V2 Escrow accounts response: {:?}", response); + + // V2 TAP receipts use different field names (payer/service_provider) but the underlying + // escrow account model is identical to V1. Both V1 and V2 receipts reference the same + // sender addresses and the same escrow relationships. + // + // V1 queries the TAP subgraph while V2 queries the network subgraph, but both return + // the same escrow account structure for processing. + // + // V2 receipt flow: + // 1. V2 receipt contains payer address (equivalent to V1 sender) + // 2. Receipt is signed by a signer authorized by the payer + // 3. Escrow accounts map: signer -> payer (sender) -> balance + // 4. Service provider (indexer) receives payments from payer's escrow + + let senders_balances: HashMap = response + .payments_escrow_accounts + .iter() + .map(|account| { + let balance = U256::checked_sub( + U256::from_str(&account.balance)?, + U256::from_str(&account.total_amount_thawing)?, + ) + .unwrap_or_else(|| { + tracing::warn!( + "Balance minus total amount thawing underflowed for V2 account {}. \ + Setting balance to 0, no V2 queries will be served for this payer.", + account.payer.id + ); + U256::from(0) + }); + + Ok((Address::from_str(&account.payer.id)?, balance)) + }) + .collect::, anyhow::Error>>()?; + + let senders_to_signers = response + .payments_escrow_accounts + .into_iter() + .map(|account| { + let payer = Address::from_str(&account.payer.id)?; + let signers = account + .payer + .signers + .iter() + .map(|signer| Address::from_str(&signer.id)) + .collect::, _>>()?; + Ok((payer, signers)) + }) + .collect::, anyhow::Error>>()?; + + Ok(EscrowAccounts::new(senders_balances, senders_to_signers)) } async fn get_escrow_accounts_v1( @@ -262,4 +338,54 @@ mod tests { ) ); } + + #[test(tokio::test)] + async fn test_current_accounts_v2() { + // Set up a mock escrow subgraph for V2 with payer fields + let mock_server = MockServer::start().await; + let escrow_subgraph = Box::leak(Box::new( + SubgraphClient::new( + reqwest::Client::new(), + None, + DeploymentDetails::for_query_url(&format!( + "{}/subgraphs/id/{}", + &mock_server.uri(), + test_assets::ESCROW_SUBGRAPH_DEPLOYMENT + )) + .unwrap(), + ) + .await, + )); + + let mock = Mock::given(method("POST")) + .and(path(format!( + "/subgraphs/id/{}", + test_assets::ESCROW_SUBGRAPH_DEPLOYMENT + ))) + .respond_with( + ResponseTemplate::new(200) + .set_body_raw(test_assets::ESCROW_QUERY_RESPONSE_V2, "application/json"), + ); + mock_server.register(mock).await; + + // Test V2 escrow accounts watcher + let mut accounts = escrow_accounts_v2( + escrow_subgraph, + test_assets::INDEXER_ADDRESS, + Duration::from_secs(60), + true, + ) + .await + .unwrap(); + accounts.changed().await.unwrap(); + + // V2 should produce identical results to V1 since they query the same data + assert_eq!( + accounts.borrow().clone(), + EscrowAccounts::new( + ESCROW_ACCOUNTS_BALANCES.to_owned(), + ESCROW_ACCOUNTS_SENDERS_TO_SIGNERS.to_owned(), + ) + ); + } } diff --git a/crates/monitor/src/horizon_detection.rs b/crates/monitor/src/horizon_detection.rs new file mode 100644 index 000000000..340905cf0 --- /dev/null +++ b/crates/monitor/src/horizon_detection.rs @@ -0,0 +1,48 @@ +// Copyright 2023-, Edge & Node, GraphOps, and Semiotic Labs. +// SPDX-License-Identifier: Apache-2.0 + +//! Horizon (V2) contract detection utilities +//! +//! This module provides functionality to detect if Horizon (V2) contracts are active +//! in the network by querying the network subgraph for PaymentsEscrow accounts. + +use anyhow::Result; +use indexer_query::horizon_detection::{self, HorizonDetectionQuery}; + +use crate::client::SubgraphClient; + +/// Detects if Horizon (V2) contracts are active in the network. +/// +/// This function queries the network subgraph to check if any PaymentsEscrow accounts exist. +/// If they do, it indicates that Horizon contracts are deployed and active. +/// +/// # Arguments +/// * `network_subgraph` - The network subgraph client to query +/// +/// # Returns +/// * `Ok(true)` if Horizon contracts are active +/// * `Ok(false)` if only legacy (V1) contracts are active +/// * `Err(...)` if there was an error querying the subgraph +pub async fn is_horizon_active(network_subgraph: &SubgraphClient) -> Result { + tracing::debug!("Checking if Horizon (V2) contracts are active in the network"); + + let response = network_subgraph + .query::(horizon_detection::Variables {}) + .await?; + + let response = response?; + + // If we find any PaymentsEscrow accounts, Horizon is active + let horizon_active = !response.payments_escrow_accounts.is_empty(); + + if horizon_active { + tracing::info!( + "Horizon (V2) contracts detected - found {} PaymentsEscrow accounts", + response.payments_escrow_accounts.len() + ); + } else { + tracing::info!("No Horizon (V2) contracts found - using legacy (V1) mode"); + } + + Ok(horizon_active) +} diff --git a/crates/monitor/src/lib.rs b/crates/monitor/src/lib.rs index 30f6f5321..6a4071649 100644 --- a/crates/monitor/src/lib.rs +++ b/crates/monitor/src/lib.rs @@ -7,6 +7,7 @@ mod client; mod deployment_to_allocation; mod dispute_manager; mod escrow_accounts; +mod horizon_detection; pub use crate::{ allocations::{indexer_allocations, AllocationWatcher}, @@ -15,7 +16,8 @@ pub use crate::{ deployment_to_allocation::{deployment_to_allocation, DeploymentToAllocationWatcher}, dispute_manager::{dispute_manager, DisputeManagerWatcher}, escrow_accounts::{ - escrow_accounts_v1, escrow_accounts_v2, EscrowAccounts, EscrowAccountsError, - EscrowAccountsWatcher, + empty_escrow_accounts_watcher, escrow_accounts_v1, escrow_accounts_v2, EscrowAccounts, + EscrowAccountsError, EscrowAccountsWatcher, }, + horizon_detection::is_horizon_active, }; diff --git a/crates/profiler/src/lib.rs b/crates/profiler/src/lib.rs index 24c60ed0e..ba7d9be65 100644 --- a/crates/profiler/src/lib.rs +++ b/crates/profiler/src/lib.rs @@ -1,13 +1,17 @@ // Copyright 2023-, Edge & Node, GraphOps, and Semiotic Labs. // SPDX-License-Identifier: Apache-2.0 -use std::fs::{self, File}; -use std::io::Write; -use std::path::{Path, PathBuf}; -use std::sync::atomic::{AtomicU64, Ordering}; -use std::sync::Arc; -use std::thread; -use std::time::{Duration, SystemTime}; +use std::{ + fs::{self, File}, + io::Write, + path::{Path, PathBuf}, + sync::{ + atomic::{AtomicU64, Ordering}, + Arc, + }, + thread, + time::{Duration, SystemTime}, +}; use chrono::{DateTime, Utc}; use pprof::protos::Message; diff --git a/crates/query/graphql/horizon_detection.query.graphql b/crates/query/graphql/horizon_detection.query.graphql new file mode 100644 index 000000000..1fe000606 --- /dev/null +++ b/crates/query/graphql/horizon_detection.query.graphql @@ -0,0 +1,8 @@ +# Query to detect if Horizon (V2) contracts are active in the network +# This query checks for the existence of PaymentsEscrow accounts which indicate V2 is active + +query HorizonDetectionQuery { + paymentsEscrowAccounts(first: 1) { + id + } +} \ No newline at end of file diff --git a/crates/query/graphql/latest_ravs_v2.query.graphql b/crates/query/graphql/latest_ravs_v2.query.graphql new file mode 100644 index 000000000..c3eacac21 --- /dev/null +++ b/crates/query/graphql/latest_ravs_v2.query.graphql @@ -0,0 +1,19 @@ +query LatestRavs( + $payer: ID! + $dataService: ID! + $serviceProvider: ID! + $collectionIds: [ID!]! +) { + latestRavs( + where: { + payer: $payer + dataService: $dataService + serviceProvider: $serviceProvider + id_in: $collectionIds + } + ) { + id + valueAggregate + timestamp + } +} \ No newline at end of file diff --git a/crates/query/graphql/network.schema.graphql b/crates/query/graphql/network.schema.graphql index c203e5517..738437c27 100644 --- a/crates/query/graphql/network.schema.graphql +++ b/crates/query/graphql/network.schema.graphql @@ -6603,6 +6603,297 @@ enum Pool_orderBy { closedAllocations } +# TAP v2 Entities +# PaymentsEscrow and GraphTallyCollector contract entities + +type PaymentsEscrowAccount @entity { + id: Bytes! + payer: Payer! + collector: Collector! + receiver: Receiver! + balance: BigInt! + totalAmountThawing: BigInt! + thawEndTimestamp: BigInt! +} + +input PaymentsEscrowAccount_filter { + id: Bytes + id_not: Bytes + id_in: [Bytes!] + id_not_in: [Bytes!] + payer: String + payer_not: String + payer_in: [String!] + payer_not_in: [String!] + payer_: Payer_filter + collector: String + collector_not: String + collector_in: [String!] + collector_not_in: [String!] + collector_: Collector_filter + receiver: String + receiver_not: String + receiver_in: [String!] + receiver_not_in: [String!] + receiver_: Receiver_filter + balance: BigInt + balance_not: BigInt + balance_gt: BigInt + balance_lt: BigInt + balance_gte: BigInt + balance_lte: BigInt + balance_in: [BigInt!] + balance_not_in: [BigInt!] + totalAmountThawing: BigInt + totalAmountThawing_not: BigInt + totalAmountThawing_gt: BigInt + totalAmountThawing_lt: BigInt + totalAmountThawing_gte: BigInt + totalAmountThawing_lte: BigInt + totalAmountThawing_in: [BigInt!] + totalAmountThawing_not_in: [BigInt!] + thawEndTimestamp: BigInt + thawEndTimestamp_not: BigInt + thawEndTimestamp_gt: BigInt + thawEndTimestamp_lt: BigInt + thawEndTimestamp_gte: BigInt + thawEndTimestamp_lte: BigInt + thawEndTimestamp_in: [BigInt!] + thawEndTimestamp_not_in: [BigInt!] + _change_block: BlockChangedFilter + and: [PaymentsEscrowAccount_filter] + or: [PaymentsEscrowAccount_filter] +} + +enum PaymentsEscrowAccount_orderBy { + id + payer + payer__id + collector + collector__id + collector__type + receiver + receiver__id + balance + totalAmountThawing + thawEndTimestamp +} + +type Collector @entity { + id: Bytes! + type: String + escrowAccounts: [PaymentsEscrowAccount!]! @derivedFrom(field: "collector") +} + +input Collector_filter { + id: Bytes + id_not: Bytes + id_in: [Bytes!] + id_not_in: [Bytes!] + type: String + type_not: String + type_in: [String!] + type_not_in: [String!] + type_contains: String + type_not_contains: String + type_starts_with: String + type_not_starts_with: String + type_ends_with: String + type_not_ends_with: String + escrowAccounts_: PaymentsEscrowAccount_filter + _change_block: BlockChangedFilter + and: [Collector_filter] + or: [Collector_filter] +} + +enum Collector_orderBy { + id + type + escrowAccounts +} + +type LatestRav @entity { + id: Bytes! + payer: Payer! + dataService: DataService! + serviceProvider: Receiver! + valueAggregate: BigInt! + timestamp: BigInt! + metadata: Bytes! + signature: Bytes! +} + +input LatestRav_filter { + id: Bytes + id_not: Bytes + id_in: [Bytes!] + id_not_in: [Bytes!] + payer: String + payer_not: String + payer_in: [String!] + payer_not_in: [String!] + payer_: Payer_filter + dataService: String + dataService_not: String + dataService_in: [String!] + dataService_not_in: [String!] + dataService_: DataService_filter + serviceProvider: String + serviceProvider_not: String + serviceProvider_in: [String!] + serviceProvider_not_in: [String!] + serviceProvider_: Receiver_filter + valueAggregate: BigInt + valueAggregate_not: BigInt + valueAggregate_gt: BigInt + valueAggregate_lt: BigInt + valueAggregate_gte: BigInt + valueAggregate_lte: BigInt + valueAggregate_in: [BigInt!] + valueAggregate_not_in: [BigInt!] + timestamp: BigInt + timestamp_not: BigInt + timestamp_gt: BigInt + timestamp_lt: BigInt + timestamp_gte: BigInt + timestamp_lte: BigInt + timestamp_in: [BigInt!] + timestamp_not_in: [BigInt!] + metadata: Bytes + metadata_not: Bytes + metadata_in: [Bytes!] + metadata_not_in: [Bytes!] + signature: Bytes + signature_not: Bytes + signature_in: [Bytes!] + signature_not_in: [Bytes!] + _change_block: BlockChangedFilter + and: [LatestRav_filter] + or: [LatestRav_filter] +} + +enum LatestRav_orderBy { + id + payer + payer__id + dataService + dataService__id + serviceProvider + serviceProvider__id + valueAggregate + timestamp + metadata + signature +} + +type DataService @entity { + id: Bytes! + latestRav: [LatestRav!]! @derivedFrom(field: "dataService") +} + +input DataService_filter { + id: Bytes + id_not: Bytes + id_in: [Bytes!] + id_not_in: [Bytes!] + latestRav_: LatestRav_filter + _change_block: BlockChangedFilter + and: [DataService_filter] + or: [DataService_filter] +} + +enum DataService_orderBy { + id + latestRav +} + +type Payer @entity { + id: Bytes! + escrowAccounts: [PaymentsEscrowAccount!]! @derivedFrom(field: "payer") + signers: [Signer!]! @derivedFrom(field: "payer") +} + +input Payer_filter { + id: Bytes + id_not: Bytes + id_in: [Bytes!] + id_not_in: [Bytes!] + escrowAccounts_: PaymentsEscrowAccount_filter + signers_: Signer_filter + _change_block: BlockChangedFilter + and: [Payer_filter] + or: [Payer_filter] +} + +enum Payer_orderBy { + id + escrowAccounts + signers +} + +type Receiver @entity { + id: Bytes! + escrowAccounts: [PaymentsEscrowAccount!]! @derivedFrom(field: "receiver") +} + +input Receiver_filter { + id: Bytes + id_not: Bytes + id_in: [Bytes!] + id_not_in: [Bytes!] + escrowAccounts_: PaymentsEscrowAccount_filter + _change_block: BlockChangedFilter + and: [Receiver_filter] + or: [Receiver_filter] +} + +enum Receiver_orderBy { + id + escrowAccounts +} + +type Signer @entity { + id: Bytes! + isAuthorized: Boolean! + payer: Payer! + thawEndTimestamp: BigInt! +} + +input Signer_filter { + id: Bytes + id_not: Bytes + id_in: [Bytes!] + id_not_in: [Bytes!] + isAuthorized: Boolean + isAuthorized_not: Boolean + isAuthorized_in: [Boolean!] + isAuthorized_not_in: [Boolean!] + payer: String + payer_not: String + payer_in: [String!] + payer_not_in: [String!] + payer_: Payer_filter + thawEndTimestamp: BigInt + thawEndTimestamp_not: BigInt + thawEndTimestamp_gt: BigInt + thawEndTimestamp_lt: BigInt + thawEndTimestamp_gte: BigInt + thawEndTimestamp_lte: BigInt + thawEndTimestamp_in: [BigInt!] + thawEndTimestamp_not_in: [BigInt!] + _change_block: BlockChangedFilter + and: [Signer_filter] + or: [Signer_filter] +} + +enum Signer_orderBy { + id + isAuthorized + payer + payer__id + thawEndTimestamp +} + type Query { graphNetwork( id: ID! @@ -8027,6 +8318,130 @@ type Query { """ subgraphError: _SubgraphErrorPolicy_! = deny ): [Delegator!]! + paymentsEscrowAccount( + id: ID! + + """ + The block at which the query should be executed. Can either be a `{ hash: + Bytes }` value containing a block hash, a `{ number: Int }` containing the + block number, or a `{ number_gte: Int }` containing the minimum block + number. In the case of `number_gte`, the query will be executed on the + latest block only if the subgraph has progressed to or past the minimum + block number. Defaults to the latest block when omitted. + """ + block: Block_height + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + ): PaymentsEscrowAccount + paymentsEscrowAccounts( + skip: Int = 0 + first: Int = 100 + orderBy: PaymentsEscrowAccount_orderBy + orderDirection: OrderDirection + where: PaymentsEscrowAccount_filter + + """ + The block at which the query should be executed. Can either be a `{ hash: + Bytes }` value containing a block hash, a `{ number: Int }` containing the + block number, or a `{ number_gte: Int }` containing the minimum block + number. In the case of `number_gte`, the query will be executed on the + latest block only if the subgraph has progressed to or past the minimum + block number. Defaults to the latest block when omitted. + """ + block: Block_height + + """ + Set to `allow` to receive data even if the subgraph has skipped over errors while syncing. + """ + subgraphError: _SubgraphErrorPolicy_! = deny + ): [PaymentsEscrowAccount!]! + collector( + id: ID! + block: Block_height + subgraphError: _SubgraphErrorPolicy_! = deny + ): Collector + collectors( + skip: Int = 0 + first: Int = 100 + orderBy: Collector_orderBy + orderDirection: OrderDirection + where: Collector_filter + block: Block_height + subgraphError: _SubgraphErrorPolicy_! = deny + ): [Collector!]! + latestRav( + id: ID! + block: Block_height + subgraphError: _SubgraphErrorPolicy_! = deny + ): LatestRav + latestRavs( + skip: Int = 0 + first: Int = 100 + orderBy: LatestRav_orderBy + orderDirection: OrderDirection + where: LatestRav_filter + block: Block_height + subgraphError: _SubgraphErrorPolicy_! = deny + ): [LatestRav!]! + dataService( + id: ID! + block: Block_height + subgraphError: _SubgraphErrorPolicy_! = deny + ): DataService + dataServices( + skip: Int = 0 + first: Int = 100 + orderBy: DataService_orderBy + orderDirection: OrderDirection + where: DataService_filter + block: Block_height + subgraphError: _SubgraphErrorPolicy_! = deny + ): [DataService!]! + payer( + id: ID! + block: Block_height + subgraphError: _SubgraphErrorPolicy_! = deny + ): Payer + payers( + skip: Int = 0 + first: Int = 100 + orderBy: Payer_orderBy + orderDirection: OrderDirection + where: Payer_filter + block: Block_height + subgraphError: _SubgraphErrorPolicy_! = deny + ): [Payer!]! + receiver( + id: ID! + block: Block_height + subgraphError: _SubgraphErrorPolicy_! = deny + ): Receiver + receivers( + skip: Int = 0 + first: Int = 100 + orderBy: Receiver_orderBy + orderDirection: OrderDirection + where: Receiver_filter + block: Block_height + subgraphError: _SubgraphErrorPolicy_! = deny + ): [Receiver!]! + signer( + id: ID! + block: Block_height + subgraphError: _SubgraphErrorPolicy_! = deny + ): Signer + signers( + skip: Int = 0 + first: Int = 100 + orderBy: Signer_orderBy + orderDirection: OrderDirection + where: Signer_filter + block: Block_height + subgraphError: _SubgraphErrorPolicy_! = deny + ): [Signer!]! """Access to subgraph metadata""" _meta(block: Block_height): _Meta_ @@ -11953,6 +12368,104 @@ type Subscription { """ subgraphError: _SubgraphErrorPolicy_! = deny ): [Transaction!]! + paymentsEscrowAccount( + id: ID! + block: Block_height + subgraphError: _SubgraphErrorPolicy_! = deny + ): PaymentsEscrowAccount + paymentsEscrowAccounts( + skip: Int = 0 + first: Int = 100 + orderBy: PaymentsEscrowAccount_orderBy + orderDirection: OrderDirection + where: PaymentsEscrowAccount_filter + block: Block_height + subgraphError: _SubgraphErrorPolicy_! = deny + ): [PaymentsEscrowAccount!]! + collector( + id: ID! + block: Block_height + subgraphError: _SubgraphErrorPolicy_! = deny + ): Collector + collectors( + skip: Int = 0 + first: Int = 100 + orderBy: Collector_orderBy + orderDirection: OrderDirection + where: Collector_filter + block: Block_height + subgraphError: _SubgraphErrorPolicy_! = deny + ): [Collector!]! + latestRav( + id: ID! + block: Block_height + subgraphError: _SubgraphErrorPolicy_! = deny + ): LatestRav + latestRavs( + skip: Int = 0 + first: Int = 100 + orderBy: LatestRav_orderBy + orderDirection: OrderDirection + where: LatestRav_filter + block: Block_height + subgraphError: _SubgraphErrorPolicy_! = deny + ): [LatestRav!]! + dataService( + id: ID! + block: Block_height + subgraphError: _SubgraphErrorPolicy_! = deny + ): DataService + dataServices( + skip: Int = 0 + first: Int = 100 + orderBy: DataService_orderBy + orderDirection: OrderDirection + where: DataService_filter + block: Block_height + subgraphError: _SubgraphErrorPolicy_! = deny + ): [DataService!]! + payer( + id: ID! + block: Block_height + subgraphError: _SubgraphErrorPolicy_! = deny + ): Payer + payers( + skip: Int = 0 + first: Int = 100 + orderBy: Payer_orderBy + orderDirection: OrderDirection + where: Payer_filter + block: Block_height + subgraphError: _SubgraphErrorPolicy_! = deny + ): [Payer!]! + receiver( + id: ID! + block: Block_height + subgraphError: _SubgraphErrorPolicy_! = deny + ): Receiver + receivers( + skip: Int = 0 + first: Int = 100 + orderBy: Receiver_orderBy + orderDirection: OrderDirection + where: Receiver_filter + block: Block_height + subgraphError: _SubgraphErrorPolicy_! = deny + ): [Receiver!]! + signer( + id: ID! + block: Block_height + subgraphError: _SubgraphErrorPolicy_! = deny + ): Signer + signers( + skip: Int = 0 + first: Int = 100 + orderBy: Signer_orderBy + orderDirection: OrderDirection + where: Signer_filter + block: Block_height + subgraphError: _SubgraphErrorPolicy_! = deny + ): [Signer!]! """Access to subgraph metadata""" _meta(block: Block_height): _Meta_ diff --git a/crates/query/graphql/network_escrow_account_v2.query.graphql b/crates/query/graphql/network_escrow_account_v2.query.graphql new file mode 100644 index 000000000..bebb21a93 --- /dev/null +++ b/crates/query/graphql/network_escrow_account_v2.query.graphql @@ -0,0 +1,33 @@ +# Network Subgraph EscrowAccount Query V2 +# +# This query retrieves V2 escrow account information from the network subgraph, +# which tracks PaymentsEscrow and GraphTallyCollector contract events. +# +# Input Variables: +# - $receiver (Bytes!): The address of the Indexer whose escrow accounts are being queried. +# - $thawEndTimestamp (BigInt!): A timestamp used to filter signers whose thaw period has ended or is about to end. +# +# Query Logic: +# - Fetches escrow accounts where the `receiver` is the provided indexer address. +# - Returns the following information for each escrow account: +# - `balance`: The current balance of the escrow account. +# - `totalAmountThawing`: The total amount currently thawing in the escrow. +# - Retrieves the payer and filters the payer's `signers` based on: +# - `thawEndTimestamp_lte`: Only includes signers whose thaw end timestamp is less than or equal to the provided $thawEndTimestamp. +# - `isAuthorized: true`: Only includes signers that are authorized. + +query NetworkEscrowAccountQueryV2($receiver: Bytes!, $thawEndTimestamp: BigInt!) { + paymentsEscrowAccounts(where: { receiver_: { id: $receiver } }) { + balance + totalAmountThawing + payer { + id + signers(where: { + thawEndTimestamp_lte: $thawEndTimestamp + isAuthorized: true + }) { + id + } + } + } +} \ No newline at end of file diff --git a/crates/query/src/lib.rs b/crates/query/src/lib.rs index 38c8a43e9..4d90725b3 100644 --- a/crates/query/src/lib.rs +++ b/crates/query/src/lib.rs @@ -123,3 +123,53 @@ pub mod closed_allocations { variables_derives = "Clone" )] pub struct TapTransactions; + +pub mod network_escrow_account_v2 { + use graphql_client::GraphQLQuery; + type BigInt = String; + type Bytes = String; + + #[derive(GraphQLQuery)] + #[graphql( + schema_path = "graphql/network.schema.graphql", + query_path = "graphql/network_escrow_account_v2.query.graphql", + response_derives = "Debug", + variables_derives = "Clone" + )] + pub struct NetworkEscrowAccountQueryV2; + + pub use network_escrow_account_query_v2::Variables; +} + +pub mod horizon_detection { + use graphql_client::GraphQLQuery; + type Bytes = String; + + #[derive(GraphQLQuery)] + #[graphql( + schema_path = "graphql/network.schema.graphql", + query_path = "graphql/horizon_detection.query.graphql", + response_derives = "Debug", + variables_derives = "Clone" + )] + pub struct HorizonDetectionQuery; + + pub use horizon_detection_query::*; +} + +pub mod latest_ravs_v2 { + use graphql_client::GraphQLQuery; + type BigInt = String; + type Bytes = String; + + #[derive(GraphQLQuery)] + #[graphql( + schema_path = "graphql/network.schema.graphql", + query_path = "graphql/latest_ravs_v2.query.graphql", + response_derives = "Debug", + variables_derives = "Clone" + )] + pub struct LatestRavs; + + pub use latest_ravs::*; +} diff --git a/crates/service/src/middleware/sender.rs b/crates/service/src/middleware/sender.rs index 4e0dc54be..1c95f4988 100644 --- a/crates/service/src/middleware/sender.rs +++ b/crates/service/src/middleware/sender.rs @@ -6,7 +6,7 @@ use axum::{ middleware::Next, response::Response, }; -use indexer_monitor::EscrowAccounts; +use indexer_monitor::{EscrowAccounts, EscrowAccountsError}; use thegraph_core::alloy::{primitives::Address, sol_types::Eip712Domain}; use tokio::sync::watch; @@ -18,9 +18,9 @@ pub struct SenderState { /// Used to recover the signer address pub domain_separator: Eip712Domain, /// Used to get the sender address given the signer address if v1 receipt - pub escrow_accounts_v1: watch::Receiver, + pub escrow_accounts_v1: Option>, /// Used to get the sender address given the signer address if v2 receipt - pub escrow_accounts_v2: watch::Receiver, + pub escrow_accounts_v2: Option>, } /// The current query Sender address @@ -48,14 +48,24 @@ pub async fn sender_middleware( if let Some(receipt) = request.extensions().get::() { let signer = receipt.recover_signer(&state.domain_separator)?; let sender = match receipt { - TapReceipt::V1(_) => state - .escrow_accounts_v1 - .borrow() - .get_sender_for_signer(&signer)?, - TapReceipt::V2(_) => state - .escrow_accounts_v2 - .borrow() - .get_sender_for_signer(&signer)?, + TapReceipt::V1(_) => { + if let Some(ref escrow_accounts_v1) = state.escrow_accounts_v1 { + escrow_accounts_v1.borrow().get_sender_for_signer(&signer)? + } else { + return Err(IndexerServiceError::EscrowAccount( + EscrowAccountsError::NoSenderFound { signer }, + )); + } + } + TapReceipt::V2(_) => { + if let Some(ref escrow_accounts_v2) = state.escrow_accounts_v2 { + escrow_accounts_v2.borrow().get_sender_for_signer(&signer)? + } else { + return Err(IndexerServiceError::EscrowAccount( + EscrowAccountsError::NoSenderFound { signer }, + )); + } + } }; request.extensions_mut().insert(Sender(sender)); } @@ -100,8 +110,8 @@ mod tests { let state = SenderState { domain_separator: test_assets::TAP_EIP712_DOMAIN.clone(), - escrow_accounts_v1, - escrow_accounts_v2, + escrow_accounts_v1: Some(escrow_accounts_v1), + escrow_accounts_v2: Some(escrow_accounts_v2), }; let middleware = from_fn_with_state(state, sender_middleware); diff --git a/crates/service/src/service.rs b/crates/service/src/service.rs index 110f1f04b..ad6662b7a 100644 --- a/crates/service/src/service.rs +++ b/crates/service/src/service.rs @@ -18,7 +18,7 @@ use indexer_dips::{ server::{DipsServer, DipsServerContext}, signers::EscrowSignerValidator, }; -use indexer_monitor::{escrow_accounts_v1, DeploymentDetails, SubgraphClient}; +use indexer_monitor::{escrow_accounts_v1, escrow_accounts_v2, DeploymentDetails, SubgraphClient}; use release::IndexerServiceRelease; use reqwest::Url; use tap_core::tap_eip712_domain; @@ -78,12 +78,7 @@ pub async fn run() -> anyhow::Result<()> { ) .await; - let escrow_subgraph = create_subgraph_client( - http_client.clone(), - &config.graph_node, - &config.subgraphs.escrow.config, - ) - .await; + // V2 escrow accounts are in the network subgraph, not a separate escrow_v2 subgraph // Establish Database connection necessary for serving indexer management // requests with defined schema @@ -105,19 +100,133 @@ pub async fn run() -> anyhow::Result<()> { let indexer_address = config.indexer.indexer_address; let ipfs_url = config.service.ipfs_url.clone(); - let router = ServiceRouter::builder() - .database(database.clone()) - .domain_separator(domain_separator.clone()) - .graph_node(config.graph_node) - .http_client(http_client) - .release(release) - .indexer(config.indexer) - .service(config.service) - .blockchain(config.blockchain) - .timestamp_buffer_secs(config.tap.rav_request.timestamp_buffer_secs) - .network_subgraph(network_subgraph, config.subgraphs.network) - .escrow_subgraph(escrow_subgraph, config.subgraphs.escrow) - .build(); + // Capture individual fields needed for DIPS before they get moved + let escrow_v1_query_url_for_dips = config.subgraphs.escrow.config.query_url.clone(); + // V2 escrow accounts are in the network subgraph + let escrow_v2_query_url_for_dips = Some(config.subgraphs.network.config.query_url.clone()); + + // Determine if we should check for Horizon contracts and potentially enable hybrid mode: + // - If horizon.enabled = false: Pure legacy mode, no Horizon detection + // - If horizon.enabled = true: Check if Horizon contracts are active in the network + let is_horizon_active = if config.horizon.enabled { + tracing::info!("Horizon migration support enabled - checking if Horizon contracts are active in the network"); + match indexer_monitor::is_horizon_active(network_subgraph).await { + Ok(active) => { + if active { + tracing::info!("Horizon contracts detected in network subgraph - enabling hybrid migration mode"); + tracing::info!("Mode: Accept new V2 receipts only, continue processing existing V1 receipts for RAVs"); + } else { + tracing::info!("Horizon contracts not yet active in network subgraph - remaining in legacy mode"); + } + active + } + Err(e) => { + tracing::warn!( + "Failed to detect Horizon contracts: {}. Remaining in legacy mode.", + e + ); + false + } + } + } else { + tracing::info!( + "Horizon migration support disabled in configuration - using pure legacy mode" + ); + false + }; + + // Configure router with escrow watchers based on automatic Horizon detection + let router = if is_horizon_active { + tracing::info!("Horizon contracts detected - using Horizon migration mode: V2 receipts only, but processing existing V1 receipts"); + + // Create V1 escrow watcher for processing existing receipts + let escrow_subgraph_v1 = create_subgraph_client( + http_client.clone(), + &config.graph_node, + &config.subgraphs.escrow.config, + ) + .await; + + let v1_watcher = indexer_monitor::escrow_accounts_v1( + escrow_subgraph_v1, + indexer_address, + config.subgraphs.escrow.config.syncing_interval_secs, + true, // Reject thawing signers eagerly + ) + .await + .expect("Error creating escrow_accounts_v1 channel"); + + // Create V2 escrow watcher for new receipts (V2 escrow accounts are in the network subgraph) + let v2_watcher = match indexer_monitor::escrow_accounts_v2( + network_subgraph, + indexer_address, + config.subgraphs.network.config.syncing_interval_secs, + true, // Reject thawing signers eagerly + ) + .await + { + Ok(watcher) => { + tracing::info!("V2 escrow accounts successfully initialized from network subgraph"); + watcher + } + Err(e) => { + tracing::error!( + "Failed to initialize V2 escrow accounts: {}. Service cannot continue.", + e + ); + std::process::exit(1); + } + }; + + ServiceRouter::builder() + .database(database.clone()) + .domain_separator(domain_separator.clone()) + .graph_node(config.graph_node) + .http_client(http_client) + .release(release) + .indexer(config.indexer) + .service(config.service) + .blockchain(config.blockchain) + .timestamp_buffer_secs(config.tap.rav_request.timestamp_buffer_secs) + .network_subgraph(network_subgraph, config.subgraphs.network) + .escrow_accounts_v1(v1_watcher) + .escrow_accounts_v2(v2_watcher) + .build() + } else { + tracing::info!( + "No Horizon contracts detected - using Legacy (V1) mode with escrow accounts v1 only" + ); + // Only create v1 watcher for legacy mode + let escrow_subgraph_v1 = create_subgraph_client( + http_client.clone(), + &config.graph_node, + &config.subgraphs.escrow.config, + ) + .await; + + let v1_watcher = indexer_monitor::escrow_accounts_v1( + escrow_subgraph_v1, + indexer_address, + config.subgraphs.escrow.config.syncing_interval_secs, + true, // Reject thawing signers eagerly + ) + .await + .expect("Error creating escrow_accounts_v1 channel"); + + ServiceRouter::builder() + .database(database.clone()) + .domain_separator(domain_separator.clone()) + .graph_node(config.graph_node) + .http_client(http_client) + .release(release) + .indexer(config.indexer) + .service(config.service) + .blockchain(config.blockchain) + .timestamp_buffer_secs(config.tap.rav_request.timestamp_buffer_secs) + .network_subgraph(network_subgraph, config.subgraphs.network) + .escrow_accounts_v1(v1_watcher) + .build() + }; serve_metrics(config.metrics.get_socket_addr()); @@ -143,14 +252,64 @@ pub async fn run() -> anyhow::Result<()> { Arc::new(IpfsClient::new(ipfs_url.as_str()).unwrap()); // TODO: Try to re-use the same watcher for both DIPS and TAP - let watcher = escrow_accounts_v1( - escrow_subgraph, - indexer_address, - Duration::from_secs(500), - true, - ) - .await - .expect("Failed to create escrow accounts watcher"); + // DIPS is part of Horizon/v2, so use v2 escrow watcher when available + let dips_http_client = reqwest::Client::builder() + .timeout(Duration::from_secs(60)) + .build() + .expect("Failed to init HTTP client"); + + let escrow_subgraph_for_dips = if let Some(ref escrow_v2_url) = escrow_v2_query_url_for_dips + { + tracing::info!("DIPS using v2 escrow subgraph"); + // Create subgraph client for v2 + Box::leak(Box::new( + SubgraphClient::new( + dips_http_client, + None, // No local deployment + DeploymentDetails::for_query_url_with_token( + escrow_v2_url.clone(), + None, // No auth token + ), + ) + .await, + )) + } else { + tracing::info!("DIPS falling back to v1 escrow subgraph"); + // Create subgraph client for v1 + Box::leak(Box::new( + SubgraphClient::new( + dips_http_client, + None, // No local deployment + DeploymentDetails::for_query_url_with_token( + escrow_v1_query_url_for_dips, + None, // No auth token + ), + ) + .await, + )) + }; + + let watcher = if escrow_v2_query_url_for_dips.is_some() { + // Use v2 watcher for DIPS when v2 is available + escrow_accounts_v2( + escrow_subgraph_for_dips, + indexer_address, + Duration::from_secs(500), + true, + ) + .await + .expect("Failed to create escrow accounts v2 watcher for DIPS") + } else { + // Fall back to v1 watcher + escrow_accounts_v1( + escrow_subgraph_for_dips, + indexer_address, + Duration::from_secs(500), + true, + ) + .await + .expect("Failed to create escrow accounts v1 watcher for DIPS") + }; let registry = NetworksRegistry::from_latest_version().await.unwrap(); diff --git a/crates/service/src/service/router.rs b/crates/service/src/service/router.rs index d6f3b59c5..5d67f33ed 100644 --- a/crates/service/src/service/router.rs +++ b/crates/service/src/service/router.rs @@ -140,33 +140,42 @@ impl ServiceRouter { // Monitor escrow accounts v1 // if not provided, create monitor from subgraph let escrow_accounts_v1 = match (self.escrow_accounts_v1, self.escrow_subgraph.as_ref()) { - (Some(escrow_account), _) => escrow_account, - (_, Some((escrow_subgraph, escrow))) => escrow_accounts_v1( - escrow_subgraph, - indexer_address, - escrow.config.syncing_interval_secs, - true, // Reject thawing signers eagerly - ) - .await - .expect("Error creating escrow_accounts channel"), - (None, None) => panic!("No escrow accounts or escrow subgraph was provided"), + (Some(escrow_account), _) => Some(escrow_account), + (_, Some((escrow_subgraph, escrow))) => Some( + escrow_accounts_v1( + escrow_subgraph, + indexer_address, + escrow.config.syncing_interval_secs, + true, // Reject thawing signers eagerly + ) + .await + .expect("Error creating escrow_accounts_v1 channel"), + ), + (None, None) => None, }; // Monitor escrow accounts v2 // if not provided, create monitor from subgraph let escrow_accounts_v2 = match (self.escrow_accounts_v2, self.escrow_subgraph.as_ref()) { - (Some(escrow_account), _) => escrow_account, - (_, Some((escrow_subgraph, escrow))) => escrow_accounts_v2( - escrow_subgraph, - indexer_address, - escrow.config.syncing_interval_secs, - true, // Reject thawing signers eagerly - ) - .await - .expect("Error creating escrow_accounts channel"), - (None, None) => panic!("No escrow accounts or escrow subgraph was provided"), + (Some(escrow_account), _) => Some(escrow_account), + (_, Some((escrow_subgraph, escrow))) => Some( + escrow_accounts_v2( + escrow_subgraph, + indexer_address, + escrow.config.syncing_interval_secs, + true, // Reject thawing signers eagerly + ) + .await + .expect("Error creating escrow_accounts_v2 channel"), + ), + (None, None) => None, }; + // Ensure at least one escrow accounts watcher is available + if escrow_accounts_v1.is_none() && escrow_accounts_v2.is_none() { + panic!("At least one escrow accounts watcher (v1 or v2) must be provided"); + } + // Monitor dispute manager address // if not provided, create monitor from subgraph let dispute_manager = match (self.dispute_manager, self.network_subgraph.as_ref()) { diff --git a/crates/service/src/tap.rs b/crates/service/src/tap.rs index 0c8cf03d5..4fa0c61d5 100644 --- a/crates/service/src/tap.rs +++ b/crates/service/src/tap.rs @@ -51,8 +51,8 @@ impl IndexerTapContext { pub async fn get_checks( pgpool: PgPool, indexer_allocations: Receiver>, - escrow_accounts_v1: Receiver, - escrow_accounts_v2: Receiver, + escrow_accounts_v1: Option>, + escrow_accounts_v2: Option>, timestamp_error_tolerance: Duration, receipt_max_value: u128, ) -> Vec> { diff --git a/crates/service/src/tap/checks/allocation_eligible.rs b/crates/service/src/tap/checks/allocation_eligible.rs index b2338e5f2..eff8c5922 100644 --- a/crates/service/src/tap/checks/allocation_eligible.rs +++ b/crates/service/src/tap/checks/allocation_eligible.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; use anyhow::anyhow; use indexer_allocation::Allocation; use tap_core::receipt::checks::{Check, CheckError, CheckResult}; -use thegraph_core::alloy::primitives::Address; +use thegraph_core::{alloy::primitives::Address, CollectionId}; use tokio::sync::watch::Receiver; use crate::tap::{CheckingReceipt, TapReceipt}; @@ -29,7 +29,12 @@ impl Check for AllocationEligible { _: &tap_core::receipt::Context, receipt: &CheckingReceipt, ) -> CheckResult { - let allocation_id = receipt.signed_receipt().allocation_id(); + let allocation_id = match receipt.signed_receipt() { + TapReceipt::V1(receipt) => receipt.message.allocation_id, + TapReceipt::V2(receipt) => { + CollectionId::from(receipt.message.collection_id).as_address() + } + }; if !self .indexer_allocations .borrow() diff --git a/crates/service/src/tap/checks/sender_balance_check.rs b/crates/service/src/tap/checks/sender_balance_check.rs index 9f09340d6..9ece8490b 100644 --- a/crates/service/src/tap/checks/sender_balance_check.rs +++ b/crates/service/src/tap/checks/sender_balance_check.rs @@ -13,14 +13,14 @@ use crate::{ }; pub struct SenderBalanceCheck { - escrow_accounts_v1: Receiver, - escrow_accounts_v2: Receiver, + escrow_accounts_v1: Option>, + escrow_accounts_v2: Option>, } impl SenderBalanceCheck { pub fn new( - escrow_accounts_v1: Receiver, - escrow_accounts_v2: Receiver, + escrow_accounts_v1: Option>, + escrow_accounts_v2: Option>, ) -> Self { Self { escrow_accounts_v1, @@ -36,17 +36,32 @@ impl Check for SenderBalanceCheck { ctx: &tap_core::receipt::Context, receipt: &CheckingReceipt, ) -> CheckResult { - let escrow_accounts_snapshot_v1 = self.escrow_accounts_v1.borrow(); - let escrow_accounts_snapshot_v2 = self.escrow_accounts_v2.borrow(); - let Sender(receipt_sender) = ctx .get::() .ok_or(CheckError::Failed(anyhow::anyhow!("Could not find sender")))?; // get balance for escrow account given receipt type let balance_result = match receipt.signed_receipt() { - TapReceipt::V1(_) => escrow_accounts_snapshot_v1.get_balance_for_sender(receipt_sender), - TapReceipt::V2(_) => escrow_accounts_snapshot_v2.get_balance_for_sender(receipt_sender), + TapReceipt::V1(_) => { + if let Some(ref escrow_accounts_v1) = self.escrow_accounts_v1 { + let escrow_accounts_snapshot_v1 = escrow_accounts_v1.borrow(); + escrow_accounts_snapshot_v1.get_balance_for_sender(receipt_sender) + } else { + return Err(CheckError::Failed(anyhow!( + "Receipt v1 received but no escrow accounts v1 watcher is available" + ))); + } + } + TapReceipt::V2(_) => { + if let Some(ref escrow_accounts_v2) = self.escrow_accounts_v2 { + let escrow_accounts_snapshot_v2 = escrow_accounts_v2.borrow(); + escrow_accounts_snapshot_v2.get_balance_for_sender(receipt_sender) + } else { + return Err(CheckError::Failed(anyhow!( + "Receipt v2 received but no escrow accounts v2 watcher is available" + ))); + } + } }; // Check that the sender has a non-zero balance -- more advanced accounting is done in diff --git a/crates/service/src/tap/receipt_store.rs b/crates/service/src/tap/receipt_store.rs index 9d209e10f..287eac208 100644 --- a/crates/service/src/tap/receipt_store.rs +++ b/crates/service/src/tap/receipt_store.rs @@ -7,7 +7,10 @@ use itertools::{Either, Itertools}; use sqlx::{types::BigDecimal, PgPool}; use tap_core::{manager::adapters::ReceiptStore, receipt::WithValueAndTimestamp}; use thegraph_core::alloy::{hex::ToHexExt, sol_types::Eip712Domain}; -use tokio::{sync::mpsc::Receiver, sync::oneshot::Sender as OneShotSender, task::JoinHandle}; +use tokio::{ + sync::{mpsc::Receiver, oneshot::Sender as OneShotSender}, + task::JoinHandle, +}; use tokio_util::sync::CancellationToken; use super::{AdapterError, CheckingReceipt, IndexerTapContext, TapReceipt}; @@ -164,7 +167,7 @@ impl InnerContext { let receipts_len = receipts.len(); let mut signers = Vec::with_capacity(receipts_len); let mut signatures = Vec::with_capacity(receipts_len); - let mut allocation_ids = Vec::with_capacity(receipts_len); + let mut collection_ids = Vec::with_capacity(receipts_len); let mut payers = Vec::with_capacity(receipts_len); let mut data_services = Vec::with_capacity(receipts_len); let mut service_providers = Vec::with_capacity(receipts_len); @@ -175,7 +178,7 @@ impl InnerContext { for receipt in receipts { signers.push(receipt.signer_address); signatures.push(receipt.signature); - allocation_ids.push(receipt.allocation_id); + collection_ids.push(receipt.collection_id); payers.push(receipt.payer); data_services.push(receipt.data_service); service_providers.push(receipt.service_provider); @@ -187,7 +190,7 @@ impl InnerContext { r#"INSERT INTO tap_horizon_receipts ( signer_address, signature, - allocation_id, + collection_id, payer, data_service, service_provider, @@ -197,7 +200,7 @@ impl InnerContext { ) SELECT * FROM UNNEST( $1::CHAR(40)[], $2::BYTEA[], - $3::CHAR(40)[], + $3::CHAR(64)[], $4::CHAR(40)[], $5::CHAR(40)[], $6::CHAR(40)[], @@ -207,7 +210,7 @@ impl InnerContext { )"#, &signers, &signatures, - &allocation_ids, + &collection_ids, &payers, &data_services, &service_providers, @@ -328,7 +331,7 @@ impl DbReceiptV1 { pub struct DbReceiptV2 { signer_address: String, signature: Vec, - allocation_id: String, + collection_id: String, payer: String, data_service: String, service_provider: String, @@ -342,7 +345,9 @@ impl DbReceiptV2 { receipt: &tap_graph::v2::SignedReceipt, separator: &Eip712Domain, ) -> anyhow::Result { - let allocation_id = receipt.message.allocation_id.encode_hex(); + let collection_id = thegraph_core::CollectionId::from(receipt.message.collection_id) + .as_address() + .encode_hex(); let payer = receipt.message.payer.encode_hex(); let data_service = receipt.message.data_service.encode_hex(); let service_provider = receipt.message.service_provider.encode_hex(); @@ -360,7 +365,7 @@ impl DbReceiptV2 { let nonce = BigDecimal::from(receipt.message.nonce); let value = BigDecimal::from(BigInt::from(receipt.value())); Ok(Self { - allocation_id, + collection_id, payer, data_service, service_provider, diff --git a/crates/tap-agent/src/agent.rs b/crates/tap-agent/src/agent.rs index 5c9fa3724..74304fa3e 100644 --- a/crates/tap-agent/src/agent.rs +++ b/crates/tap-agent/src/agent.rs @@ -165,28 +165,58 @@ pub async fn start_agent() -> (ActorRef, JoinHandl .await .expect("Error creating escrow_accounts channel"); + // V2 escrow accounts are in the network subgraph, not a separate TAP v2 subgraph let escrow_accounts_v2 = escrow_accounts_v2( - escrow_subgraph, + network_subgraph, *indexer_address, - *escrow_sync_interval, + *network_sync_interval, false, ) .await - .expect("Error creating escrow_accounts channel"); + .expect("Error creating escrow_accounts_v2 channel"); + + // Determine if we should check for Horizon contracts and potentially enable hybrid mode: + // - If horizon.enabled = false: Pure legacy mode, no Horizon detection + // - If horizon.enabled = true: Check if Horizon contracts are active in the network + let is_horizon_enabled = if CONFIG.horizon.enabled { + tracing::info!("Horizon migration support enabled - checking if Horizon contracts are active in the network"); + match indexer_monitor::is_horizon_active(network_subgraph).await { + Ok(active) => { + if active { + tracing::info!("Horizon contracts detected in network subgraph - enabling hybrid migration mode"); + tracing::info!("TAP Agent Mode: Process existing V1 receipts for RAVs, accept new V2 receipts"); + } else { + tracing::info!("Horizon contracts not yet active in network subgraph - remaining in legacy mode"); + } + active + } + Err(e) => { + tracing::warn!( + "Failed to detect Horizon contracts: {}. Remaining in legacy mode.", + e + ); + false + } + } + } else { + tracing::info!( + "Horizon migration support disabled in configuration - using pure legacy mode" + ); + false + }; + + // In both modes we need both watchers for the hybrid processing + let (escrow_accounts_v1_final, escrow_accounts_v2_final) = if is_horizon_enabled { + tracing::info!("TAP Agent: Horizon migration mode - processing existing V1 receipts and new V2 receipts"); + (escrow_accounts_v1, escrow_accounts_v2) + } else { + tracing::info!("TAP Agent: Legacy mode - V1 receipts only"); + (escrow_accounts_v1, escrow_accounts_v2) // Still keep V2 watcher for consistency + }; let config = Box::leak(Box::new({ let mut config = SenderAccountConfig::from_config(&CONFIG); - // FIXME: This is a temporary measure to disable - // Horizon, even if enabled through our configuration file. - // Force disable Horizon support - config.horizon_enabled = false; - // Add a warning log so operators know their setting was ignore - if CONFIG.horizon.enabled { - tracing::warn!( - "Horizon support is configured as enabled but has been forcibly disabled as it's not fully supported yet. \ - This is a temporary measure until Horizon support is stable." - ); - } + config.horizon_enabled = is_horizon_enabled; config })); @@ -195,8 +225,8 @@ pub async fn start_agent() -> (ActorRef, JoinHandl domain_separator: EIP_712_DOMAIN.clone(), pgpool, indexer_allocations, - escrow_accounts_v1, - escrow_accounts_v2, + escrow_accounts_v1: escrow_accounts_v1_final, + escrow_accounts_v2: escrow_accounts_v2_final, escrow_subgraph, network_subgraph, sender_aggregator_endpoints: sender_aggregator_endpoints.clone(), diff --git a/crates/tap-agent/src/agent/sender_account.rs b/crates/tap-agent/src/agent/sender_account.rs index 46ccb0f5a..35226fd45 100644 --- a/crates/tap-agent/src/agent/sender_account.rs +++ b/crates/tap-agent/src/agent/sender_account.rs @@ -25,10 +25,13 @@ use tap_aggregator::grpc::{ v1::tap_aggregator_client::TapAggregatorClient as AggregatorV1, v2::tap_aggregator_client::TapAggregatorClient as AggregatorV2, }; -use thegraph_core::alloy::{ - hex::ToHexExt, - primitives::{Address, U256}, - sol_types::Eip712Domain, +use thegraph_core::{ + alloy::{ + hex::ToHexExt, + primitives::{Address, U256}, + sol_types::Eip712Domain, + }, + AllocationId as AllocationIdCore, CollectionId, }; use tokio::{sync::watch::Receiver, task::JoinHandle}; use tonic::transport::{Channel, Endpoint}; @@ -144,7 +147,8 @@ impl From for RavInformation { impl From<&tap_graph::v2::SignedRav> for RavInformation { fn from(value: &tap_graph::v2::SignedRav) -> Self { RavInformation { - allocation_id: value.message.allocationId, + allocation_id: AllocationIdCore::from(CollectionId::from(value.message.collectionId)) + .into_inner(), value_aggregate: value.message.valueAggregate, } } @@ -213,9 +217,9 @@ pub enum SenderAccountMessage { /// as well as requesting the underlaying allocation rav request /// /// Custom behavior is defined in [ReceiptFees] - UpdateReceiptFees(Address, ReceiptFees), + UpdateReceiptFees(AllocationId, ReceiptFees), /// Updates the counter for invalid receipts and verify to deny sender - UpdateInvalidReceiptFees(Address, UnaggregatedReceipts), + UpdateInvalidReceiptFees(AllocationId, UnaggregatedReceipts), /// Update rav tracker UpdateRav(RavInformation), #[cfg(test)] @@ -439,6 +443,18 @@ impl State { "SenderAccount is creating allocation." ); + // Check if actor already exists to prevent race condition during concurrent creation attempts + let actor_name = self.format_sender_allocation(&allocation_id.address()); + if ActorRef::::where_is(actor_name.clone()).is_some() { + tracing::debug!( + %self.sender, + %allocation_id, + actor_name = %actor_name, + "SenderAllocation actor already exists, skipping creation" + ); + return Ok(()); + } + match allocation_id { AllocationId::Legacy(id) => { let args = SenderAllocationArgs::builder() @@ -474,7 +490,7 @@ impl State { .build(); SenderAllocation::::spawn_linked( - Some(self.format_sender_allocation(&id)), + Some(self.format_sender_allocation(&id.as_address())), SenderAllocation::default(), args, sender_account_ref.get_cell(), @@ -627,14 +643,6 @@ impl State { sender_balance = self.sender_balance.to_u128(), "Denying sender." ); - // Check if this is horizon like sender and if it is actually enable, - // otherwise just ignore. - // FIXME: This should be removed once full horizon support - // is implemented! - if matches!(self.sender_type, SenderType::Horizon) && !self.config.horizon_enabled { - return; - } - SenderAccount::deny_sender(self.sender_type, &self.pgpool, self.sender).await; self.denied = true; SENDER_DENIED @@ -815,7 +823,7 @@ impl Actor for SenderAccount { if config.horizon_enabled { sqlx::query!( r#" - SELECT allocation_id, value_aggregate + SELECT collection_id, value_aggregate FROM tap_horizon_ravs WHERE payer = $1 AND last AND NOT final; "#, @@ -825,7 +833,7 @@ impl Actor for SenderAccount { .await .expect("Should not fail to fetch from \"horizon\" scalar_tap_ravs") .into_iter() - .map(|record| (record.allocation_id, record.value_aggregate)) + .map(|record| (record.collection_id, record.value_aggregate)) .collect() } else { vec![] @@ -861,14 +869,85 @@ impl Actor for SenderAccount { _ => vec![], } } - // TODO Implement query for unfinalized v2 transactions - // Depends on Escrow Subgraph Schema SenderType::Horizon => { if config.horizon_enabled { - todo!("Implement query for unfinalized v2 transactions, It depends on Escrow Subgraph Schema") + // V2 doesn't have transaction tracking like V1, but we can check if the RAVs + // we're about to redeem are still the latest ones by querying LatestRavs. + // If the subgraph has newer RAVs, it means ours were already redeemed. + use indexer_query::latest_ravs_v2::{self, LatestRavs}; + + let collection_ids: Vec = last_non_final_ravs + .iter() + .map(|(collection_id, _)| collection_id.clone()) + .collect(); + + if !collection_ids.is_empty() { + // For V2, use the indexer address as the data service since the indexer + // is providing the data service for the queries + let data_service = config.indexer_address; + + match escrow_subgraph + .query::(latest_ravs_v2::Variables { + payer: format!("{sender_id:x?}"), + data_service: format!("{data_service:x?}"), + service_provider: format!("{:x?}", config.indexer_address), + collection_ids: collection_ids.clone(), + }) + .await + { + Ok(Ok(response)) => { + // Create a map of our current RAVs for easy lookup + let our_ravs: HashMap = last_non_final_ravs + .iter() + .map(|(collection_id, value)| { + let value_u128 = value + .to_bigint() + .and_then(|v| v.to_u128()) + .unwrap_or(0); + (collection_id.clone(), value_u128) + }) + .collect(); + + // Check which RAVs have been updated (indicating redemption) + let mut finalized_allocation_ids = vec![]; + for rav in response.latest_ravs { + if let Some(&our_value) = our_ravs.get(&rav.id) { + // If the subgraph RAV has higher value, our RAV was redeemed + if let Ok(subgraph_value) = + rav.value_aggregate.parse::() + { + if subgraph_value > our_value { + // Return collection ID string for filtering + finalized_allocation_ids.push(rav.id); + } + } + } + } + finalized_allocation_ids + } + Ok(Err(e)) => { + tracing::warn!( + error = %e, + sender = %sender_id, + "Failed to query V2 latest RAVs, assuming none are finalized" + ); + vec![] + } + Err(e) => { + tracing::warn!( + error = %e, + sender = %sender_id, + "Failed to execute V2 latest RAVs query, assuming none are finalized" + ); + vec![] + } + } + } else { + vec![] + } + } else { + vec![] } - // if we have any problems, we don't want to filter out - vec![] } }; @@ -1060,7 +1139,7 @@ impl Actor for SenderAccount { state .invalid_receipts_tracker - .update(allocation_id, unaggregated_fees.value); + .update(allocation_id.address(), unaggregated_fees.value); // invalid receipts can't go down let should_deny = !state.denied && state.deny_condition_reached(); @@ -1097,7 +1176,7 @@ impl Actor for SenderAccount { // add new value state .sender_fee_tracker - .add(allocation_id, value, timestamp_ns); + .add(allocation_id.address(), value, timestamp_ns); SENDER_FEE_TRACKER .with_label_values(&[&state.sender.to_string()]) @@ -1110,16 +1189,16 @@ impl Actor for SenderAccount { .set( state .sender_fee_tracker - .get_total_fee_for_allocation(&allocation_id) + .get_total_fee_for_allocation(&allocation_id.address()) .map(|fee| fee.value) .unwrap_or_default() as f64, ); } ReceiptFees::RavRequestResponse(fees, rav_result) => { - state.finalize_rav_request(allocation_id, (fees, rav_result)); + state.finalize_rav_request(allocation_id.address(), (fees, rav_result)); } ReceiptFees::UpdateValue(unaggregated_fees) => { - state.update_sender_fee(allocation_id, unaggregated_fees); + state.update_sender_fee(allocation_id.address(), unaggregated_fees); } ReceiptFees::Retry => {} } @@ -1137,8 +1216,10 @@ impl Actor for SenderAccount { let total_fee_outside_buffer = state.sender_fee_tracker.get_ravable_total_fee(); let total_counter_for_allocation = state .sender_fee_tracker - .get_count_outside_buffer_for_allocation(&allocation_id); - let can_trigger_rav = state.sender_fee_tracker.can_trigger_rav(allocation_id); + .get_count_outside_buffer_for_allocation(&allocation_id.address()); + let can_trigger_rav = state + .sender_fee_tracker + .can_trigger_rav(allocation_id.address()); let counter_greater_receipt_limit = total_counter_for_allocation >= state.config.rav_request_receipt_limit && can_trigger_rav; @@ -1158,7 +1239,9 @@ impl Actor for SenderAccount { %allocation_id, "Total counter greater than the receipt limit per rav. Triggering RAV request" ); - state.rav_request_for_allocation(allocation_id).await + state + .rav_request_for_allocation(allocation_id.address()) + .await } else { Ok(()) }; @@ -1367,7 +1450,7 @@ impl Actor for SenderAccount { // check for deny conditions let _ = myself.cast(SenderAccountMessage::UpdateReceiptFees( - allocation_id, + AllocationId::Legacy(AllocationIdCore::from(allocation_id)), ReceiptFees::Retry, )); @@ -1471,7 +1554,10 @@ pub mod tests { flush_messages, pgpool, ALLOCATION_ID_0, ALLOCATION_ID_1, TAP_SENDER as SENDER, TAP_SIGNER as SIGNER, }; - use thegraph_core::alloy::{hex::ToHexExt, primitives::U256}; + use thegraph_core::{ + alloy::{hex::ToHexExt, primitives::U256}, + AllocationId as AllocationIdCore, + }; use tokio::sync::mpsc; use wiremock::{ matchers::{body_string_contains, method}, @@ -1565,7 +1651,9 @@ pub mod tests { .call() .await; - let allocation_ids = HashSet::from_iter([AllocationId::Legacy(ALLOCATION_ID_0)]); + let allocation_ids = HashSet::from_iter([AllocationId::Legacy(AllocationIdCore::from( + ALLOCATION_ID_0, + ))]); // we expect it to create a sender allocation sender_account .cast(SenderAccountMessage::UpdateAllocationIds( @@ -1659,7 +1747,7 @@ pub mod tests { // we expect it to create a sender allocation sender_account .cast(SenderAccountMessage::NewAllocationId(AllocationId::Legacy( - ALLOCATION_ID_0, + AllocationIdCore::from(ALLOCATION_ID_0), ))) .unwrap(); @@ -1673,9 +1761,11 @@ pub mod tests { // nothing should change because we already created sender_account .cast(SenderAccountMessage::UpdateAllocationIds( - vec![AllocationId::Legacy(ALLOCATION_ID_0)] - .into_iter() - .collect(), + vec![AllocationId::Legacy(AllocationIdCore::from( + ALLOCATION_ID_0, + ))] + .into_iter() + .collect(), )) .unwrap(); @@ -1753,7 +1843,7 @@ pub mod tests { basic_sender_account .sender_account .cast(SenderAccountMessage::UpdateReceiptFees( - ALLOCATION_ID_0, + AllocationId::Legacy(AllocationIdCore::from(ALLOCATION_ID_0)), ReceiptFees::NewReceipt(TRIGGER_VALUE - 1, get_current_timestamp_u64_ns()), )) .unwrap(); @@ -1783,7 +1873,7 @@ pub mod tests { basic_sender_account .sender_account .cast(SenderAccountMessage::UpdateReceiptFees( - ALLOCATION_ID_0, + AllocationId::Legacy(AllocationIdCore::from(ALLOCATION_ID_0)), ReceiptFees::NewReceipt(TRIGGER_VALUE, get_current_timestamp_u64_ns()), )) .unwrap(); @@ -1797,7 +1887,7 @@ pub mod tests { basic_sender_account .sender_account .cast(SenderAccountMessage::UpdateReceiptFees( - ALLOCATION_ID_0, + AllocationId::Legacy(AllocationIdCore::from(ALLOCATION_ID_0)), ReceiptFees::Retry, )) .unwrap(); @@ -1826,7 +1916,7 @@ pub mod tests { sender_account .cast(SenderAccountMessage::UpdateReceiptFees( - ALLOCATION_ID_0, + AllocationId::Legacy(AllocationIdCore::from(ALLOCATION_ID_0)), ReceiptFees::NewReceipt(1, get_current_timestamp_u64_ns()), )) .unwrap(); @@ -1836,7 +1926,7 @@ pub mod tests { sender_account .cast(SenderAccountMessage::UpdateReceiptFees( - ALLOCATION_ID_0, + AllocationId::Legacy(AllocationIdCore::from(ALLOCATION_ID_0)), ReceiptFees::NewReceipt(1, get_current_timestamp_u64_ns()), )) .unwrap(); @@ -1847,7 +1937,7 @@ pub mod tests { sender_account .cast(SenderAccountMessage::UpdateReceiptFees( - ALLOCATION_ID_0, + AllocationId::Legacy(AllocationIdCore::from(ALLOCATION_ID_0)), ReceiptFees::Retry, )) .unwrap(); @@ -1866,9 +1956,11 @@ pub mod tests { let (sender_account, _, prefix, _) = create_sender_account() .pgpool(pgpool) .initial_allocation( - vec![AllocationId::Legacy(ALLOCATION_ID_0)] - .into_iter() - .collect(), + vec![AllocationId::Legacy(AllocationIdCore::from( + ALLOCATION_ID_0, + ))] + .into_iter() + .collect(), ) .escrow_subgraph_endpoint(&mock_escrow_subgraph.uri()) .call() @@ -1953,7 +2045,7 @@ pub mod tests { sender_account .cast(SenderAccountMessage::UpdateReceiptFees( - ALLOCATION_ID_0, + AllocationId::Legacy(AllocationIdCore::from(ALLOCATION_ID_0)), ReceiptFees::NewReceipt(TRIGGER_VALUE, get_current_timestamp_u64_ns()), )) .unwrap(); @@ -1989,7 +2081,7 @@ pub mod tests { ($value:expr) => { sender_account .cast(SenderAccountMessage::UpdateReceiptFees( - ALLOCATION_ID_0, + AllocationId::Legacy(AllocationIdCore::from(ALLOCATION_ID_0)), ReceiptFees::UpdateValue(UnaggregatedReceipts { value: $value, last_id: 11, @@ -2006,7 +2098,7 @@ pub mod tests { ($value:expr) => { sender_account .cast(SenderAccountMessage::UpdateInvalidReceiptFees( - ALLOCATION_ID_0, + AllocationId::Legacy(AllocationIdCore::from(ALLOCATION_ID_0)), UnaggregatedReceipts { value: $value, last_id: 11, @@ -2142,7 +2234,7 @@ pub mod tests { ($value:expr) => { sender_account .cast(SenderAccountMessage::UpdateReceiptFees( - ALLOCATION_ID_0, + AllocationId::Legacy(AllocationIdCore::from(ALLOCATION_ID_0)), ReceiptFees::UpdateValue(UnaggregatedReceipts { value: $value, last_id: 11, @@ -2432,7 +2524,7 @@ pub mod tests { // set retry sender_account .cast(SenderAccountMessage::UpdateReceiptFees( - ALLOCATION_ID_0, + AllocationId::Legacy(AllocationIdCore::from(ALLOCATION_ID_0)), ReceiptFees::NewReceipt(TRIGGER_VALUE, get_current_timestamp_u64_ns()), )) .unwrap(); @@ -2440,9 +2532,9 @@ pub mod tests { assert!(matches!( msg, SenderAccountMessage::UpdateReceiptFees( - ALLOCATION_ID_0, + AllocationId::Legacy(allocation_id), ReceiptFees::NewReceipt(TRIGGER_VALUE, _) - ) + ) if allocation_id == AllocationIdCore::from(ALLOCATION_ID_0) )); let deny = call!(sender_account, SenderAccountMessage::GetDeny).unwrap(); diff --git a/crates/tap-agent/src/agent/sender_accounts_manager.rs b/crates/tap-agent/src/agent/sender_accounts_manager.rs index 8a9ae7a29..2e7bedc29 100644 --- a/crates/tap-agent/src/agent/sender_accounts_manager.rs +++ b/crates/tap-agent/src/agent/sender_accounts_manager.rs @@ -19,7 +19,10 @@ use ractor::{Actor, ActorCell, ActorProcessingErr, ActorRef, SupervisionEvent}; use reqwest::Url; use serde::Deserialize; use sqlx::{postgres::PgListener, PgPool}; -use thegraph_core::alloy::{primitives::Address, sol_types::Eip712Domain}; +use thegraph_core::{ + alloy::{primitives::Address, sol_types::Eip712Domain}, + AllocationId as AllocationIdCore, CollectionId, +}; use tokio::{select, sync::watch::Receiver}; use super::sender_account::{ @@ -36,14 +39,14 @@ static RECEIPTS_CREATED: LazyLock = LazyLock::new(|| { .unwrap() }); -/// Notification received by pgnotify +/// Notification received by pgnotify for V1 (legacy) receipts /// -/// This contains a list of properties that are sent by postgres when a receipt is inserted +/// This contains a list of properties that are sent by postgres when a V1 receipt is inserted #[derive(Deserialize, Debug, PartialEq, Eq, Clone)] -pub struct NewReceiptNotification { +pub struct NewReceiptNotificationV1 { /// id inside the table pub id: u64, - /// address of the allocation + /// address of the allocation (V1 uses 20-byte allocation_id) pub allocation_id: Address, /// address of wallet that signed this receipt pub signer_address: Address, @@ -53,35 +56,130 @@ pub struct NewReceiptNotification { pub value: u128, } +/// Notification received by pgnotify for V2 (Horizon) receipts +/// +/// This contains a list of properties that are sent by postgres when a V2 receipt is inserted +#[derive(Deserialize, Debug, PartialEq, Eq, Clone)] +pub struct NewReceiptNotificationV2 { + /// id inside the table + pub id: u64, + /// collection id (V2 uses 32-byte collection_id) + pub collection_id: String, // 64-character hex string from database + /// address of wallet that signed this receipt + pub signer_address: Address, + /// timestamp of the receipt + pub timestamp_ns: u64, + /// value of the receipt + pub value: u128, +} + +/// Unified notification that can represent both V1 and V2 receipts +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum NewReceiptNotification { + /// V1 (Legacy) receipt notification with allocation_id + V1(NewReceiptNotificationV1), + /// V2 (Horizon) receipt notification with collection_id + V2(NewReceiptNotificationV2), +} + +impl NewReceiptNotification { + /// Get the ID regardless of version + pub fn id(&self) -> u64 { + match self { + NewReceiptNotification::V1(n) => n.id, + NewReceiptNotification::V2(n) => n.id, + } + } + + /// Get the signer address regardless of version + pub fn signer_address(&self) -> Address { + match self { + NewReceiptNotification::V1(n) => n.signer_address, + NewReceiptNotification::V2(n) => n.signer_address, + } + } + + /// Get the timestamp regardless of version + pub fn timestamp_ns(&self) -> u64 { + match self { + NewReceiptNotification::V1(n) => n.timestamp_ns, + NewReceiptNotification::V2(n) => n.timestamp_ns, + } + } + + /// Get the value regardless of version + pub fn value(&self) -> u128 { + match self { + NewReceiptNotification::V1(n) => n.value, + NewReceiptNotification::V2(n) => n.value, + } + } + + /// Get the allocation ID as a unified type + pub fn allocation_id(&self) -> AllocationId { + match self { + NewReceiptNotification::V1(n) => { + AllocationId::Legacy(AllocationIdCore::from(n.allocation_id)) + } + NewReceiptNotification::V2(n) => { + // Convert the hex string to CollectionId + let collection_id = CollectionId::from_str(&n.collection_id) + .expect("Valid collection_id in database"); + AllocationId::Horizon(collection_id) + } + } + } +} + /// Manager Actor #[derive(Debug, Clone)] pub struct SenderAccountsManager; -/// Wrapped AllocationId Address with two possible variants +/// Wrapped AllocationId with two possible variants /// /// This is used by children actors to define what kind of /// SenderAllocation must be created to handle the correct /// Rav and Receipt types #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum AllocationId { - /// Legacy allocation - Legacy(Address), - /// New Subgraph DataService allocation - Horizon(Address), + /// Legacy allocation using AllocationId from thegraph-core + Legacy(AllocationIdCore), + /// New Subgraph DataService allocation using CollectionId + Horizon(CollectionId), } impl AllocationId { - /// Take the inner address for both allocation types + /// Get a hex string representation for database queries + pub fn to_hex(&self) -> String { + match self { + AllocationId::Legacy(allocation_id) => allocation_id.to_string(), + AllocationId::Horizon(collection_id) => collection_id.to_string(), + } + } + + /// Get the underlying Address for Legacy allocations + pub fn as_address(&self) -> Option
{ + match self { + AllocationId::Legacy(allocation_id) => Some(**allocation_id), + AllocationId::Horizon(_) => None, + } + } + + /// Get an Address representation for both allocation types pub fn address(&self) -> Address { match self { - AllocationId::Legacy(address) | AllocationId::Horizon(address) => *address, + AllocationId::Legacy(allocation_id) => **allocation_id, + AllocationId::Horizon(collection_id) => collection_id.as_address(), } } } impl Display for AllocationId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.address().fmt(f) + match self { + AllocationId::Legacy(allocation_id) => write!(f, "{allocation_id}"), + AllocationId::Horizon(collection_id) => write!(f, "{collection_id}"), + } } } @@ -192,7 +290,7 @@ impl Actor for SenderAccountsManager { .keys() .cloned() // TODO: map based on the allocation type returned by the subgraph - .map(AllocationId::Legacy) + .map(|addr| AllocationId::Legacy(AllocationIdCore::from(addr))) .collect::>() }); // we need two connections because each one will listen to different notify events @@ -619,8 +717,8 @@ impl State { .iter() .map(|allocation_id| { AllocationId::Legacy( - Address::from_str(allocation_id) - .expect("allocation_id should be a valid address"), + AllocationIdCore::from_str(allocation_id) + .expect("allocation_id should be a valid allocation ID"), ) }) .collect::>(); @@ -661,8 +759,8 @@ impl State { .iter() .map(|allocation_id| { AllocationId::Legacy( - Address::from_str(allocation_id) - .expect("allocation_id should be a valid address"), + AllocationIdCore::from_str(allocation_id) + .expect("allocation_id should be a valid allocation ID"), ) }) .collect::>(); @@ -697,16 +795,16 @@ impl State { let mut unfinalized_sender_allocations_map: HashMap> = HashMap::new(); - let receipts_signer_allocations_in_db = sqlx::query!( + let receipts_signer_collections_in_db = sqlx::query!( r#" WITH grouped AS ( - SELECT signer_address, allocation_id + SELECT signer_address, collection_id FROM tap_horizon_receipts - GROUP BY signer_address, allocation_id + GROUP BY signer_address, collection_id ) SELECT signer_address, - ARRAY_AGG(allocation_id) AS allocation_ids + ARRAY_AGG(collection_id) AS collection_ids FROM grouped GROUP BY signer_address "# @@ -715,22 +813,40 @@ impl State { .await .expect("should be able to fetch pending V2 receipts from the database"); - for row in receipts_signer_allocations_in_db { - let allocation_ids = row - .allocation_ids - .expect("all receipts V2 should have an allocation_id") - .iter() - .map(|allocation_id| { - AllocationId::Legacy( - Address::from_str(allocation_id) - .expect("allocation_id should be a valid address"), - ) - }) - .collect::>(); + for row in receipts_signer_collections_in_db { + let collection_ids = + row.collection_ids + .expect("all receipts V2 should have a collection_id") + .iter() + .map(|collection_id| { + let trimmed = collection_id.trim(); + let hex_str = if let Some(stripped) = trimmed.strip_prefix("0x") { + stripped + } else { + trimmed + }; + + // For migration period: collection_id in DB is actually a 20-byte address + // that needs to be converted to a 32-byte CollectionId + if hex_str.len() == 40 { + // 20-byte address -> convert to CollectionId using From
+ let address = Address::from_str(&format!("0x{hex_str}")) + .unwrap_or_else(|e| panic!("Invalid address '{trimmed}': {e}")); + AllocationId::Horizon(CollectionId::from(address)) + } else if hex_str.len() == 64 { + // 32-byte CollectionId + AllocationId::Horizon(CollectionId::from_str(&format!("0x{hex_str}")).unwrap_or_else(|e| { + panic!("Invalid collection_id '{trimmed}': {e}") + })) + } else { + panic!("Invalid collection_id length '{}': expected 40 or 64 hex characters, got {}", trimmed, hex_str.len()) + } + }) + .collect::>(); let signer_id = Address::from_str(&row.signer_address) .expect("signer_address should be a valid address"); let sender_id = self - .escrow_accounts_v1 + .escrow_accounts_v2 .borrow() .get_sender_for_signer(&signer_id) .expect("should be able to get sender from signer"); @@ -739,14 +855,14 @@ impl State { unfinalized_sender_allocations_map .entry(sender_id) .or_default() - .extend(allocation_ids); + .extend(collection_ids); } let nonfinal_ravs_sender_allocations_in_db = sqlx::query!( r#" SELECT payer, - ARRAY_AGG(DISTINCT allocation_id) FILTER (WHERE NOT last) AS allocation_ids + ARRAY_AGG(DISTINCT collection_id) FILTER (WHERE NOT last) AS allocation_ids FROM tap_horizon_ravs GROUP BY payer "# @@ -762,10 +878,10 @@ impl State { if let Some(allocation_id_strings) = row.allocation_ids { let allocation_ids = allocation_id_strings .iter() - .map(|allocation_id| { - AllocationId::Legacy( - Address::from_str(allocation_id) - .expect("allocation_id should be a valid address"), + .map(|collection_id| { + AllocationId::Horizon( + CollectionId::from_str(collection_id) + .expect("collection_id should be a valid collection ID"), ) }) .collect::>(); @@ -865,14 +981,43 @@ async fn new_receipts_watcher( ); break; }; - let Ok(new_receipt_notification) = - serde_json::from_str::(pg_notification.payload()) - else { - tracing::error!( - "should be able to deserialize the Postgres Notify event payload as a \ - NewReceiptNotification", - ); - break; + // Determine notification format based on the channel name + let new_receipt_notification = match pg_notification.channel() { + "scalar_tap_receipt_notification" => { + // V1 notification format + match serde_json::from_str::(pg_notification.payload()) { + Ok(v1_notif) => NewReceiptNotification::V1(v1_notif), + Err(e) => { + tracing::error!( + "Failed to deserialize V1 notification payload: {}, payload: {}", + e, + pg_notification.payload() + ); + break; + } + } + } + "tap_horizon_receipt_notification" => { + // V2 notification format + match serde_json::from_str::(pg_notification.payload()) { + Ok(v2_notif) => NewReceiptNotification::V2(v2_notif), + Err(e) => { + tracing::error!( + "Failed to deserialize V2 notification payload: {}, payload: {}", + e, + pg_notification.payload() + ); + break; + } + } + } + unknown_channel => { + tracing::error!( + "Received notification from unknown channel: {}", + unknown_channel + ); + break; + } }; if let Err(e) = handle_notification( new_receipt_notification, @@ -916,21 +1061,27 @@ async fn handle_notification( let Ok(sender_address) = escrow_accounts_rx .borrow() - .get_sender_for_signer(&new_receipt_notification.signer_address) + .get_sender_for_signer(&new_receipt_notification.signer_address()) else { // TODO: save the receipt in the failed receipts table? bail!( "No sender address found for receipt signer address {}. \ This should not happen.", - new_receipt_notification.signer_address + new_receipt_notification.signer_address() ); }; - let allocation_id = &new_receipt_notification.allocation_id; - let allocation_str = &allocation_id.to_string(); + let allocation_id = new_receipt_notification.allocation_id(); + let allocation_str = allocation_id.to_hex(); + + // For actor lookup, use the address format that matches how actors are created + let allocation_for_actor_name = match &allocation_id { + AllocationId::Legacy(id) => id.to_string(), + AllocationId::Horizon(collection_id) => collection_id.as_address().to_string(), + }; let actor_name = format!( - "{}{sender_address}:{allocation_id}", + "{}{sender_address}:{allocation_for_actor_name}", prefix .as_ref() .map_or(String::default(), |prefix| format!("{prefix}:")) @@ -962,10 +1113,7 @@ async fn handle_notification( ); }; sender_account - .cast(SenderAccountMessage::NewAllocationId(match sender_type { - SenderType::Legacy => AllocationId::Legacy(*allocation_id), - SenderType::Horizon => AllocationId::Horizon(*allocation_id), - })) + .cast(SenderAccountMessage::NewAllocationId(allocation_id)) .map_err(|e| { anyhow!( "Error while sendeing new allocation id message to sender_account: {:?}", @@ -987,7 +1135,7 @@ async fn handle_notification( })?; RECEIPTS_CREATED - .with_label_values(&[&sender_address.to_string(), allocation_str]) + .with_label_values(&[&sender_address.to_string(), &allocation_str]) .inc(); Ok(()) } @@ -1011,11 +1159,14 @@ mod tests { watch, }; - use super::{new_receipts_watcher, SenderAccountsManagerMessage, State}; + use super::{ + new_receipts_watcher, NewReceiptNotification, NewReceiptNotificationV1, + SenderAccountsManagerMessage, State, + }; use crate::{ agent::{ sender_account::SenderAccountMessage, - sender_accounts_manager::{handle_notification, NewReceiptNotification, SenderType}, + sender_accounts_manager::{handle_notification, SenderType}, }, test::{ actors::{DummyActor, MockSenderAccount, MockSenderAllocation, TestableActor}, @@ -1313,7 +1464,7 @@ mod tests { for i in 1..=receipts_count { let receipt = receipts.recv().await.unwrap(); - assert_eq!(i, receipt.id); + assert_eq!(i, receipt.id()); } assert_eq!(receipts.try_recv().unwrap_err(), TryRecvError::Empty); @@ -1372,13 +1523,13 @@ mod tests { .await .unwrap(); - let new_receipt_notification = NewReceiptNotification { + let new_receipt_notification = NewReceiptNotification::V1(NewReceiptNotificationV1 { id: 1, allocation_id: ALLOCATION_ID_0, signer_address: SIGNER.1, timestamp_ns: 1, value: 1, - }; + }); handle_notification( new_receipt_notification, diff --git a/crates/tap-agent/src/agent/sender_allocation.rs b/crates/tap-agent/src/agent/sender_allocation.rs index 1611db2bd..a5d2f3f11 100644 --- a/crates/tap-agent/src/agent/sender_allocation.rs +++ b/crates/tap-agent/src/agent/sender_allocation.rs @@ -139,8 +139,8 @@ pub struct SenderAllocationState { pgpool: PgPool, /// Instance of TapManager for our [NetworkVersion] T tap_manager: TapManager, - /// Current allocation address - allocation_id: Address, + /// Current allocation/collection identifier + allocation_id: T::AllocationId, /// Address of the sender responsible for this [SenderAllocation] sender: Address, /// Address of the indexer @@ -196,8 +196,8 @@ impl AllocationConfig { pub struct SenderAllocationArgs { /// Database connection pub pgpool: PgPool, - /// Current allocation address - pub allocation_id: Address, + /// Current allocation/collection identifier + pub allocation_id: T::AllocationId, /// Address of the sender responsible for this [SenderAllocation] pub sender: Address, /// Watcher containing the escrow accounts @@ -266,14 +266,14 @@ where args: Self::Arguments, ) -> Result { let sender_account_ref = args.sender_account_ref.clone(); - let allocation_id = args.allocation_id; + let allocation_id = args.allocation_id.clone(); let mut state = SenderAllocationState::new(args).await?; // update invalid receipts state.invalid_receipts_fees = state.calculate_invalid_receipts_fee().await?; if state.invalid_receipts_fees.value > 0 { sender_account_ref.cast(SenderAccountMessage::UpdateInvalidReceiptFees( - allocation_id, + T::to_allocation_id_enum(&allocation_id), state.invalid_receipts_fees, ))?; } @@ -282,7 +282,7 @@ where state.unaggregated_fees = state.recalculate_all_unaggregated_fees().await?; sender_account_ref.cast(SenderAccountMessage::UpdateReceiptFees( - allocation_id, + T::to_allocation_id_enum(&allocation_id), ReceiptFees::UpdateValue(state.unaggregated_fees), ))?; @@ -372,12 +372,9 @@ where match message { SenderAllocationMessage::NewReceipt(notification) => { - let NewReceiptNotification { - id, - value: fees, - timestamp_ns, - .. - } = notification; + let id = notification.id(); + let fees = notification.value(); + let timestamp_ns = notification.timestamp_ns(); if id <= unaggregated_fees.last_id { // Unexpected: received a receipt with an ID not greater than the last processed one tracing::warn!( @@ -407,7 +404,7 @@ where state .sender_account_ref .cast(SenderAccountMessage::UpdateReceiptFees( - state.allocation_id, + T::to_allocation_id_enum(&state.allocation_id), ReceiptFees::NewReceipt(fees, timestamp_ns), ))?; } @@ -420,7 +417,7 @@ where state .sender_account_ref .cast(SenderAccountMessage::UpdateReceiptFees( - state.allocation_id, + T::to_allocation_id_enum(&state.allocation_id), ReceiptFees::RavRequestResponse( state.unaggregated_fees, rav_result.map(|res| res.map(Into::into)), @@ -469,7 +466,7 @@ where config.indexer_address, config.escrow_polling_interval, sender, - allocation_id, + T::allocation_id_to_address(&allocation_id), escrow_subgraph, ) .await, @@ -481,7 +478,7 @@ where ]; let context = TapAgentContext::builder() .pgpool(pgpool.clone()) - .allocation_id(allocation_id) + .allocation_id(T::allocation_id_to_address(&allocation_id)) .indexer_address(config.indexer_address) .sender(sender) .escrow_accounts(escrow_accounts.clone()) @@ -732,7 +729,7 @@ where }); self.sender_account_ref .cast(SenderAccountMessage::UpdateInvalidReceiptFees( - self.allocation_id, + T::to_allocation_id_enum(&self.allocation_id), self.invalid_receipts_fees, ))?; @@ -803,7 +800,7 @@ where ) .execute(&self.pgpool) .await - .map_err(|e| { + .map_err(|e: sqlx::Error| { tracing::error!("Failed to store invalid receipt: {}", e); anyhow!(e) })?; @@ -818,7 +815,7 @@ where let reciepts_len = receipts.len(); let mut reciepts_signers = Vec::with_capacity(reciepts_len); let mut encoded_signatures = Vec::with_capacity(reciepts_len); - let mut allocation_ids = Vec::with_capacity(reciepts_len); + let mut collection_ids = Vec::with_capacity(reciepts_len); let mut payers = Vec::with_capacity(reciepts_len); let mut data_services = Vec::with_capacity(reciepts_len); let mut service_providers = Vec::with_capacity(reciepts_len); @@ -828,7 +825,7 @@ where let mut error_logs = Vec::with_capacity(reciepts_len); for (receipt, receipt_error) in receipts { - let allocation_id = receipt.message.allocation_id; + let collection_id = receipt.message.collection_id; let payer = receipt.message.payer; let data_service = receipt.message.data_service; let service_provider = receipt.message.service_provider; @@ -841,13 +838,13 @@ where })?; tracing::debug!( "Receipt for allocation {} and signer {} failed reason: {}", - allocation_id.encode_hex(), + collection_id.encode_hex(), receipt_signer.encode_hex(), receipt_error ); reciepts_signers.push(receipt_signer.encode_hex()); encoded_signatures.push(encoded_signature); - allocation_ids.push(allocation_id.encode_hex()); + collection_ids.push(collection_id.encode_hex()); payers.push(payer.encode_hex()); data_services.push(data_service.encode_hex()); service_providers.push(service_provider.encode_hex()); @@ -860,7 +857,7 @@ where r#"INSERT INTO tap_horizon_receipts_invalid ( signer_address, signature, - allocation_id, + collection_id, payer, data_service, service_provider, @@ -871,7 +868,7 @@ where ) SELECT * FROM UNNEST( $1::CHAR(40)[], $2::BYTEA[], - $3::CHAR(40)[], + $3::CHAR(64)[], $4::CHAR(40)[], $5::CHAR(40)[], $6::CHAR(40)[], @@ -882,7 +879,7 @@ where )"#, &reciepts_signers, &encoded_signatures, - &allocation_ids, + &collection_ids, &payers, &data_services, &service_providers, @@ -893,7 +890,7 @@ where ) .execute(&self.pgpool) .await - .map_err(|e| { + .map_err(|e: sqlx::Error| { tracing::error!("Failed to store invalid receipt: {}", e); anyhow!(e) })?; @@ -921,7 +918,7 @@ where ) VALUES ($1, $2, $3, $4, $5) "#, - self.allocation_id.encode_hex(), + T::allocation_id_to_address(&self.allocation_id).encode_hex(), self.sender.encode_hex(), serde_json::to_value(expected_rav)?, serde_json::to_value(rav)?, @@ -977,7 +974,7 @@ impl DatabaseInteractions for SenderAllocationState { "#, BigDecimal::from(min_timestamp), BigDecimal::from(max_timestamp), - self.allocation_id.encode_hex(), + (**self.allocation_id).encode_hex(), &signers, ) .execute(&self.pgpool) @@ -1000,7 +997,7 @@ impl DatabaseInteractions for SenderAllocationState { allocation_id = $1 AND signer_address IN (SELECT unnest($2::text[])) "#, - self.allocation_id.encode_hex(), + (**self.allocation_id).encode_hex(), &signers ) .fetch_one(&self.pgpool) @@ -1050,7 +1047,7 @@ impl DatabaseInteractions for SenderAllocationState { AND signer_address IN (SELECT unnest($3::text[])) AND timestamp_ns > $4 "#, - self.allocation_id.encode_hex(), + (**self.allocation_id).encode_hex(), last_id, &signers, BigDecimal::from( @@ -1096,7 +1093,7 @@ impl DatabaseInteractions for SenderAllocationState { SET last = true WHERE allocation_id = $1 AND sender_address = $2 "#, - self.allocation_id.encode_hex(), + (**self.allocation_id).encode_hex(), self.sender.encode_hex(), ) .execute(&self.pgpool) @@ -1131,14 +1128,16 @@ impl DatabaseInteractions for SenderAllocationState { ) -> anyhow::Result<()> { sqlx::query!( r#" - DELETE FROM scalar_tap_receipts + DELETE FROM tap_horizon_receipts WHERE timestamp_ns BETWEEN $1 AND $2 - AND allocation_id = $3 - AND signer_address IN (SELECT unnest($4::text[])); + AND collection_id = $3 + AND service_provider = $4 + AND signer_address IN (SELECT unnest($5::text[])); "#, BigDecimal::from(min_timestamp), BigDecimal::from(max_timestamp), - self.allocation_id.encode_hex(), + self.allocation_id.to_string(), + self.indexer_address.encode_hex(), &signers, ) .execute(&self.pgpool) @@ -1159,10 +1158,10 @@ impl DatabaseInteractions for SenderAllocationState { FROM tap_horizon_receipts_invalid WHERE - allocation_id = $1 + collection_id = $1 AND signer_address IN (SELECT unnest($2::text[])) "#, - self.allocation_id.encode_hex(), + self.allocation_id.to_string(), &signers ) .fetch_one(&self.pgpool) @@ -1205,13 +1204,13 @@ impl DatabaseInteractions for SenderAllocationState { FROM tap_horizon_receipts WHERE - allocation_id = $1 + collection_id = $1 AND service_provider = $2 AND id <= $3 AND signer_address IN (SELECT unnest($4::text[])) AND timestamp_ns > $5 "#, - self.allocation_id.encode_hex(), + self.allocation_id.to_string(), self.indexer_address.encode_hex(), last_id, &signers, @@ -1258,11 +1257,11 @@ impl DatabaseInteractions for SenderAllocationState { UPDATE tap_horizon_ravs SET last = true WHERE - allocation_id = $1 + collection_id = $1 AND payer = $2 AND service_provider = $3 "#, - self.allocation_id.encode_hex(), + self.allocation_id.to_string(), self.sender.encode_hex(), self.indexer_address.encode_hex(), ) @@ -1316,6 +1315,7 @@ pub mod tests { flush_messages, pgpool, ALLOCATION_ID_0, TAP_EIP712_DOMAIN as TAP_EIP712_DOMAIN_SEPARATOR, TAP_SENDER as SENDER, TAP_SIGNER as SIGNER, }; + use thegraph_core::AllocationId as AllocationIdCore; use tokio::sync::{mpsc, watch}; use tonic::{transport::Endpoint, Code}; use wiremock::{ @@ -1330,7 +1330,9 @@ pub mod tests { use crate::{ agent::{ sender_account::{ReceiptFees, SenderAccountMessage}, - sender_accounts_manager::NewReceiptNotification, + sender_accounts_manager::{ + AllocationId, NewReceiptNotification, NewReceiptNotificationV1, + }, sender_allocation::DatabaseInteractions, }, tap::{context::Legacy, CheckingReceipt}, @@ -1424,7 +1426,7 @@ pub mod tests { SenderAllocationArgs::builder() .pgpool(pgpool.clone()) - .allocation_id(ALLOCATION_ID_0) + .allocation_id(AllocationIdCore::from(ALLOCATION_ID_0)) .sender(SENDER.1) .escrow_accounts(escrow_accounts_rx) .escrow_subgraph(escrow_subgraph) @@ -1564,13 +1566,15 @@ pub mod tests { // should validate with id less than last_id cast!( sender_allocation, - SenderAllocationMessage::NewReceipt(NewReceiptNotification { - id: 0, - value: 10, - allocation_id: ALLOCATION_ID_0, - signer_address: SIGNER.1, - timestamp_ns: 0, - }) + SenderAllocationMessage::NewReceipt(NewReceiptNotification::V1( + NewReceiptNotificationV1 { + id: 0, + value: 10, + allocation_id: ALLOCATION_ID_0, + signer_address: SIGNER.1, + timestamp_ns: 0, + } + )) ) .unwrap(); @@ -1581,13 +1585,15 @@ pub mod tests { cast!( sender_allocation, - SenderAllocationMessage::NewReceipt(NewReceiptNotification { - id: 1, - value: 20, - allocation_id: ALLOCATION_ID_0, - signer_address: SIGNER.1, - timestamp_ns, - }) + SenderAllocationMessage::NewReceipt(NewReceiptNotification::V1( + NewReceiptNotificationV1 { + id: 1, + value: 20, + allocation_id: ALLOCATION_ID_0, + signer_address: SIGNER.1, + timestamp_ns, + } + )) ) .unwrap(); @@ -1599,7 +1605,7 @@ pub mod tests { let last_message_emitted = message_receiver.recv().await.unwrap(); let expected_message = SenderAccountMessage::UpdateReceiptFees( - ALLOCATION_ID_0, + AllocationId::Legacy(AllocationIdCore::from(ALLOCATION_ID_0)), ReceiptFees::NewReceipt(20u128, timestamp_ns), ); assert_eq!(last_message_emitted, expected_message); diff --git a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_account__tests__update_allocation_ids-2.snap b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_account__tests__update_allocation_ids-2.snap index ef0269c0b..aa4876e84 100644 --- a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_account__tests__update_allocation_ids-2.snap +++ b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_account__tests__update_allocation_ids-2.snap @@ -3,7 +3,9 @@ source: crates/tap-agent/src/agent/sender_account.rs expression: message --- UpdateReceiptFees( - 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + Legacy( + 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + ), UpdateValue( UnaggregatedReceipts { value: 0, diff --git a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__close_allocation_no_pending_fees.snap b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__close_allocation_no_pending_fees.snap index 9d3934e1f..bf8c99bf5 100644 --- a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__close_allocation_no_pending_fees.snap +++ b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__close_allocation_no_pending_fees.snap @@ -4,7 +4,9 @@ expression: message_receiver.recv().await --- Some( UpdateReceiptFees( - 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + Legacy( + 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + ), UpdateValue( UnaggregatedReceipts { value: 0, diff --git a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__execute-2.snap b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__execute-2.snap index 4fb719143..4d4d0e73a 100644 --- a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__execute-2.snap +++ b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__execute-2.snap @@ -3,7 +3,9 @@ source: crates/tap-agent/src/agent/sender_allocation.rs expression: startup_msg --- UpdateReceiptFees( - 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + Legacy( + 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + ), UpdateValue( UnaggregatedReceipts { value: 499500, diff --git a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__execute.snap b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__execute.snap index 4fb719143..4d4d0e73a 100644 --- a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__execute.snap +++ b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__execute.snap @@ -3,7 +3,9 @@ source: crates/tap-agent/src/agent/sender_allocation.rs expression: startup_msg --- UpdateReceiptFees( - 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + Legacy( + 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + ), UpdateValue( UnaggregatedReceipts { value: 499500, diff --git a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__failed_rav_request-2.snap b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__failed_rav_request-2.snap index 390b67866..42cd281d6 100644 --- a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__failed_rav_request-2.snap +++ b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__failed_rav_request-2.snap @@ -1,9 +1,11 @@ --- source: crates/tap-agent/src/agent/sender_allocation.rs -expression: rav_response_message +expression: rav_error_response_message --- UpdateReceiptFees( - 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + Legacy( + 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + ), RavRequestResponse( UnaggregatedReceipts { value: 45, diff --git a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__failed_rav_request.snap b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__failed_rav_request.snap index 155e493df..ceda1251e 100644 --- a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__failed_rav_request.snap +++ b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__failed_rav_request.snap @@ -3,7 +3,9 @@ source: crates/tap-agent/src/agent/sender_allocation.rs expression: startup_msg --- UpdateReceiptFees( - 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + Legacy( + 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + ), UpdateValue( UnaggregatedReceipts { value: 45, diff --git a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__rav_request_when_all_receipts_invalid-2.snap b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__rav_request_when_all_receipts_invalid-2.snap index f977ceac9..3611a76dc 100644 --- a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__rav_request_when_all_receipts_invalid-2.snap +++ b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__rav_request_when_all_receipts_invalid-2.snap @@ -3,7 +3,9 @@ source: crates/tap-agent/src/agent/sender_allocation.rs expression: invalid_receipts --- UpdateInvalidReceiptFees( - 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + Legacy( + 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + ), UnaggregatedReceipts { value: 16220184412847561580, last_id: 0, diff --git a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__rav_request_when_all_receipts_invalid-3.snap b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__rav_request_when_all_receipts_invalid-3.snap index 2d8170404..bf8aed574 100644 --- a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__rav_request_when_all_receipts_invalid-3.snap +++ b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__rav_request_when_all_receipts_invalid-3.snap @@ -3,7 +3,9 @@ source: crates/tap-agent/src/agent/sender_allocation.rs expression: rav_error_response_message --- UpdateReceiptFees( - 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + Legacy( + 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + ), RavRequestResponse( UnaggregatedReceipts { value: 0, diff --git a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__rav_request_when_all_receipts_invalid.snap b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__rav_request_when_all_receipts_invalid.snap index 408b3bdc1..04518e546 100644 --- a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__rav_request_when_all_receipts_invalid.snap +++ b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__rav_request_when_all_receipts_invalid.snap @@ -3,7 +3,9 @@ source: crates/tap-agent/src/agent/sender_allocation.rs expression: startup_msg --- UpdateReceiptFees( - 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + Legacy( + 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + ), UpdateValue( UnaggregatedReceipts { value: 16220184412847561580, diff --git a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__receive_new_receipt.snap b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__receive_new_receipt.snap index 5acfb2b0d..f9e21b0f2 100644 --- a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__receive_new_receipt.snap +++ b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__receive_new_receipt.snap @@ -3,7 +3,9 @@ source: crates/tap-agent/src/agent/sender_allocation.rs expression: startup_load_msg --- UpdateReceiptFees( - 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + Legacy( + 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + ), UpdateValue( UnaggregatedReceipts { value: 0, diff --git a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__should_return_invalid_receipts_on_startup-2.snap b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__should_return_invalid_receipts_on_startup-2.snap index 688a7d38c..906c3927c 100644 --- a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__should_return_invalid_receipts_on_startup-2.snap +++ b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__should_return_invalid_receipts_on_startup-2.snap @@ -3,7 +3,9 @@ source: crates/tap-agent/src/agent/sender_allocation.rs expression: last_message_emitted --- UpdateReceiptFees( - 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + Legacy( + 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + ), UpdateValue( UnaggregatedReceipts { value: 0, diff --git a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__should_return_invalid_receipts_on_startup.snap b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__should_return_invalid_receipts_on_startup.snap index 226cf567e..607beb0b7 100644 --- a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__should_return_invalid_receipts_on_startup.snap +++ b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__should_return_invalid_receipts_on_startup.snap @@ -3,7 +3,9 @@ source: crates/tap-agent/src/agent/sender_allocation.rs expression: update_invalid_msg --- UpdateInvalidReceiptFees( - 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + Legacy( + 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + ), UnaggregatedReceipts { value: 55, last_id: 10, diff --git a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__should_update_unaggregated_fees_on_start.snap b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__should_update_unaggregated_fees_on_start.snap index bd5403702..869e8870e 100644 --- a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__should_update_unaggregated_fees_on_start.snap +++ b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__should_update_unaggregated_fees_on_start.snap @@ -3,7 +3,9 @@ source: crates/tap-agent/src/agent/sender_allocation.rs expression: last_message_emitted --- UpdateReceiptFees( - 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + Legacy( + 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + ), UpdateValue( UnaggregatedReceipts { value: 55, diff --git a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__trigger_rav_request-2.snap b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__trigger_rav_request-2.snap index 43602262d..7b4d079b6 100644 --- a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__trigger_rav_request-2.snap +++ b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__trigger_rav_request-2.snap @@ -3,7 +3,9 @@ source: crates/tap-agent/src/agent/sender_allocation.rs expression: msg --- UpdateInvalidReceiptFees( - 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + Legacy( + 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + ), UnaggregatedReceipts { value: 45, last_id: 0, diff --git a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__trigger_rav_request-3.snap b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__trigger_rav_request-3.snap index c35d23b96..754a915fe 100644 --- a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__trigger_rav_request-3.snap +++ b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__trigger_rav_request-3.snap @@ -1,9 +1,11 @@ --- source: crates/tap-agent/src/agent/sender_allocation.rs -expression: x +expression: updated_receipt_fees --- UpdateReceiptFees( - 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + Legacy( + 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + ), RavRequestResponse( UnaggregatedReceipts { value: 0, diff --git a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__trigger_rav_request.snap b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__trigger_rav_request.snap index 208b1ba24..67f5f933c 100644 --- a/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__trigger_rav_request.snap +++ b/crates/tap-agent/src/agent/snapshots/indexer_tap_agent__agent__sender_allocation__tests__trigger_rav_request.snap @@ -3,7 +3,9 @@ source: crates/tap-agent/src/agent/sender_allocation.rs expression: startup_msg --- UpdateReceiptFees( - 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + Legacy( + 0xfa44c72b753a66591f241c7dc04e8178c30e13af, + ), UpdateValue( UnaggregatedReceipts { value: 90, diff --git a/crates/tap-agent/src/tap/context.rs b/crates/tap-agent/src/tap/context.rs index 34fc2f422..e46938730 100644 --- a/crates/tap-agent/src/tap/context.rs +++ b/crates/tap-agent/src/tap/context.rs @@ -14,7 +14,10 @@ use tap_core::{ receipt::{rav::Aggregate, WithValueAndTimestamp}, signed_message::Eip712SignedMessage, }; -use thegraph_core::alloy::{primitives::Address, sol_types::SolStruct}; +use thegraph_core::{ + alloy::{primitives::Address, sol_types::SolStruct}, + AllocationId as AllocationIdCore, CollectionId, +}; use tokio::sync::watch::Receiver; pub mod checks; @@ -35,6 +38,18 @@ use tonic::{transport::Channel, Code, Status}; /// multiple matches. This way we can keep the code separated and we /// can easily add or remove network versions. pub trait NetworkVersion: Send + Sync + 'static { + /// The type used for allocation identifiers + /// Legacy uses AllocationId (20 bytes), Horizon uses CollectionId (32 bytes) for collection IDs + type AllocationId: Send + Sync + Clone + std::fmt::Debug + std::fmt::Display; + + /// Extract an Address for legacy compatibility (used for messaging) + fn allocation_id_to_address(id: &Self::AllocationId) -> Address; + + /// Convert to the AllocationId enum for messaging + fn to_allocation_id_enum( + id: &Self::AllocationId, + ) -> crate::agent::sender_accounts_manager::AllocationId; + /// Sol struct returned from an aggregation /// /// Usually this is wrapped around a [Eip712SignedMessage]. @@ -84,10 +99,21 @@ pub enum Legacy {} pub enum Horizon {} impl NetworkVersion for Legacy { + type AllocationId = AllocationIdCore; type Rav = tap_graph::ReceiptAggregateVoucher; type AggregatorClient = tap_aggregator::grpc::v1::tap_aggregator_client::TapAggregatorClient; + fn allocation_id_to_address(id: &Self::AllocationId) -> Address { + **id // AllocationIdCore derefs to Address + } + + fn to_allocation_id_enum( + id: &Self::AllocationId, + ) -> crate::agent::sender_accounts_manager::AllocationId { + crate::agent::sender_accounts_manager::AllocationId::Legacy(*id) + } + async fn aggregate( client: &mut Self::AggregatorClient, valid_receipts: Vec, @@ -117,10 +143,21 @@ impl NetworkVersion for Legacy { } impl NetworkVersion for Horizon { + type AllocationId = CollectionId; type Rav = tap_graph::v2::ReceiptAggregateVoucher; type AggregatorClient = tap_aggregator::grpc::v2::tap_aggregator_client::TapAggregatorClient; + fn allocation_id_to_address(id: &Self::AllocationId) -> Address { + id.as_address() + } + + fn to_allocation_id_enum( + id: &Self::AllocationId, + ) -> crate::agent::sender_accounts_manager::AllocationId { + crate::agent::sender_accounts_manager::AllocationId::Horizon(*id) + } + async fn aggregate( client: &mut Self::AggregatorClient, valid_receipts: Vec, @@ -155,6 +192,8 @@ impl NetworkVersion for Horizon { #[derive(Clone, bon::Builder)] pub struct TapAgentContext { pgpool: PgPool, + /// For Legacy network: represents an allocation ID + /// For Horizon network: represents a collection ID (stored in collection_id database column) #[cfg_attr(test, builder(default = crate::test::ALLOCATION_ID_0))] allocation_id: Address, #[cfg_attr(test, builder(default = test_assets::TAP_SENDER.1))] diff --git a/crates/tap-agent/src/tap/context/checks/allocation_id.rs b/crates/tap-agent/src/tap/context/checks/allocation_id.rs index 609da5176..cb6165175 100644 --- a/crates/tap-agent/src/tap/context/checks/allocation_id.rs +++ b/crates/tap-agent/src/tap/context/checks/allocation_id.rs @@ -54,19 +54,22 @@ impl Check for AllocationId { _: &tap_core::receipt::Context, receipt: &CheckingReceipt, ) -> CheckResult { - let allocation_id = receipt.signed_receipt().allocation_id(); + let allocation_id = receipt + .signed_receipt() + .allocation_id() + .ok_or_else(|| CheckError::Failed(anyhow!("Receipt does not have an allocation_id")))?; // TODO: Remove the if block below? Each TAP Monitor is specific to an allocation // ID. So the receipts that are received here should already have been filtered by // allocation ID. if allocation_id != self.allocation_id { - return Err(CheckError::Failed(anyhow!("Receipt allocation_id different from expected: allocation_id: {}, expected_allocation_id: {}", allocation_id, self.allocation_id))); + return Err(CheckError::Failed(anyhow!("Receipt allocation_id different from expected: allocation_id: {:?}, expected_allocation_id: {}", allocation_id, self.allocation_id))); }; // Check that the allocation ID is not redeemed yet for this consumer match *self.tap_allocation_redeemed.borrow() { false => Ok(()), true => Err(CheckError::Failed(anyhow!( - "Allocation {} already redeemed", + "Allocation {:?} already redeemed", allocation_id ))), } diff --git a/crates/tap-agent/src/tap/context/rav.rs b/crates/tap-agent/src/tap/context/rav.rs index 517ef5ec7..00f9d4b8a 100644 --- a/crates/tap-agent/src/tap/context/rav.rs +++ b/crates/tap-agent/src/tap/context/rav.rs @@ -12,9 +12,12 @@ use tap_core::manager::adapters::{RavRead, RavStore}; use tap_graph::{ReceiptAggregateVoucher, SignedRav}; #[allow(deprecated)] use thegraph_core::alloy::signers::Signature; -use thegraph_core::alloy::{ - hex::ToHexExt, - primitives::{Address, Bytes}, +use thegraph_core::{ + alloy::{ + hex::ToHexExt, + primitives::{Address, Bytes, FixedBytes}, + }, + CollectionId, }; use super::{error::AdapterError, Horizon, Legacy, TapAgentContext}; @@ -150,12 +153,11 @@ impl RavRead for TapAgentContext Result, Self::AdapterError> { - // TODO add data service filter let row = sqlx::query!( r#" SELECT signature, - allocation_id, + collection_id, payer, data_service, service_provider, @@ -164,12 +166,14 @@ impl RavRead for TapAgentContext for TapAgentContext::from_str(&row.collection_id).map_err(|e| { + AdapterError::RavRead { + error: format!( + "Error decoding collection_id while retrieving RAV from database: {e}" ), + } })?; let payer = Address::from_str(&row.payer).map_err(|e| AdapterError::RavRead { @@ -237,7 +243,7 @@ impl RavRead for TapAgentContext for TapAgentContext for TapAgentContext for TapAgentContext { error: format!("{e:?}."), })?; - // TODO filter by data_service when we have multiple data services - let records = sqlx::query!( r#" SELECT id, signature, - allocation_id, + collection_id, payer, data_service, service_provider, @@ -234,17 +238,19 @@ impl ReceiptRead for TapAgentContext { value FROM tap_horizon_receipts WHERE - allocation_id = $1 + collection_id = $1 AND payer = $2 - AND service_provider = $3 - AND signer_address IN (SELECT unnest($4::text[])) - AND $5::numrange @> timestamp_ns + AND data_service = $3 + AND service_provider = $4 + AND signer_address IN (SELECT unnest($5::text[])) + AND $6::numrange @> timestamp_ns ORDER BY timestamp_ns ASC - LIMIT $6 + LIMIT $7 "#, - self.allocation_id.encode_hex(), + CollectionId::from(self.allocation_id).encode_hex(), self.sender.encode_hex(), self.indexer_address.encode_hex(), + self.indexer_address.encode_hex(), &signers, rangebounds_to_pgrange(timestamp_range_ns), (receipts_limit + 1) as i64, @@ -260,10 +266,10 @@ impl ReceiptRead for TapAgentContext { "Error decoding signature while retrieving receipt from database: {e}" ), })?; - let allocation_id = Address::from_str(&record.allocation_id).map_err(|e| { + let collection_id = FixedBytes::<32>::from_str(&record.collection_id).map_err(|e| { AdapterError::ReceiptRead { error: format!( - "Error decoding allocation_id while retrieving receipt from database: {e}" + "Error decoding collection_id while retrieving receipt from database: {e}" ), } })?; @@ -312,7 +318,7 @@ impl ReceiptRead for TapAgentContext { payer, data_service, service_provider, - allocation_id, + collection_id, timestamp_ns, nonce, value, @@ -354,17 +360,19 @@ impl ReceiptDelete for TapAgentContext { r#" DELETE FROM tap_horizon_receipts WHERE - allocation_id = $1 + collection_id = $1 AND signer_address IN (SELECT unnest($2::text[])) AND $3::numrange @> timestamp_ns AND payer = $4 - AND service_provider = $5 + AND data_service = $5 + AND service_provider = $6 "#, - self.allocation_id.encode_hex(), + CollectionId::from(self.allocation_id).encode_hex(), &signers, rangebounds_to_pgrange(timestamp_ns), self.sender.encode_hex(), self.indexer_address.encode_hex(), + self.indexer_address.encode_hex(), ) .execute(&self.pgpool) .await?; @@ -449,7 +457,7 @@ mod test { #[future(awt)] context: TapAgentContext, ) where - T: CreateReceipt, + T: CreateReceipt, TapAgentContext: ReceiptRead + ReceiptDelete, { let received_receipt = @@ -489,9 +497,16 @@ mod test { let received_receipt_vec: Vec<_> = received_receipt_vec .iter() .filter(|received_receipt| { + use thegraph_core::CollectionId; + let expected_collection_id = *CollectionId::from(storage_adapter.allocation_id); + + let id_matches = received_receipt.signed_receipt().allocation_id() + == Some(storage_adapter.allocation_id) + || received_receipt.signed_receipt().collection_id() + == Some(expected_collection_id); + range.contains(&received_receipt.signed_receipt().timestamp_ns()) - && (received_receipt.signed_receipt().allocation_id() - == storage_adapter.allocation_id) + && id_matches && escrow_accounts_snapshot .get_sender_for_signer( &received_receipt @@ -560,13 +575,15 @@ mod test { .zip(received_receipt_vec.iter()) .collect::>(); - // Remove the received receipts by timestamp range for the correct (allocation_id, + // Remove the received receipts by timestamp range for the correct (collection_id, // sender) let received_receipt_vec: Vec<_> = received_receipt_vec .iter() .filter(|(_, received_receipt)| { - if (received_receipt.signed_receipt().allocation_id() - == storage_adapter.allocation_id) + use thegraph_core::CollectionId; + let expected_collection_id = *CollectionId::from(storage_adapter.allocation_id); + if (received_receipt.signed_receipt().collection_id() + == Some(expected_collection_id)) && escrow_accounts_snapshot .get_sender_for_signer( &received_receipt @@ -595,7 +612,7 @@ mod test { r#" SELECT signature, - allocation_id, + collection_id, payer, data_service, service_provider, @@ -616,7 +633,7 @@ mod test { .into_iter() .map(|record| { let signature = record.signature.as_slice().try_into().unwrap(); - let allocation_id = Address::from_str(&record.allocation_id).unwrap(); + let collection_id = FixedBytes::<32>::from_str(&record.collection_id).unwrap(); let payer = Address::from_str(&record.payer).unwrap(); let data_service = Address::from_str(&record.data_service).unwrap(); let service_provider = Address::from_str(&record.service_provider).unwrap(); @@ -633,7 +650,7 @@ mod test { let signed_receipt = tap_graph::v2::SignedReceipt { message: tap_graph::v2::Receipt { - allocation_id, + collection_id, payer, data_service, service_provider, @@ -709,7 +726,7 @@ mod test { .iter() .filter(|(_, received_receipt)| { if (received_receipt.signed_receipt().allocation_id() - == storage_adapter.allocation_id) + == Some(storage_adapter.allocation_id)) && escrow_accounts_snapshot .get_sender_for_signer( &received_receipt @@ -818,7 +835,7 @@ mod test { #[future(awt)] context: TapAgentContext, ) where - T: CreateReceipt, + T: CreateReceipt, TapAgentContext: ReceiptRead + ReceiptDelete, { // Creating 100 receipts with timestamps 42 to 141 @@ -885,7 +902,7 @@ mod test { #[future(awt)] context: TapAgentContext, ) where - T: CreateReceipt, + T: CreateReceipt, TapAgentContext: ReceiptRead + ReceiptDelete, { // Creating 10 receipts with timestamps 42 to 51 @@ -1010,7 +1027,7 @@ mod test { #[future(awt)] context: TapAgentContext, ) where - T: CreateReceipt + RemoveRange, + T: CreateReceipt + RemoveRange, TapAgentContext: ReceiptRead + ReceiptDelete, { // Creating 10 receipts with timestamps 42 to 51 diff --git a/crates/tap-agent/src/test.rs b/crates/tap-agent/src/test.rs index 3fc45d5f3..40b1bc627 100644 --- a/crates/tap-agent/src/test.rs +++ b/crates/tap-agent/src/test.rs @@ -23,7 +23,7 @@ use tap_core::{signed_message::Eip712SignedMessage, tap_eip712_domain}; use tap_graph::{Receipt, ReceiptAggregateVoucher, SignedRav, SignedReceipt}; use test_assets::{flush_messages, TAP_SENDER as SENDER, TAP_SIGNER as SIGNER}; use thegraph_core::alloy::{ - primitives::{hex::ToHexExt, Address, Bytes, U256}, + primitives::{hex::ToHexExt, Address, Bytes, FixedBytes, U256}, signers::local::{coins_bip39::English, MnemonicBuilder, PrivateKeySigner}, sol_types::Eip712Domain, }; @@ -262,13 +262,10 @@ pub async fn create_sender_accounts_manager( ) } -/// Generic implementation of create_rav +/// Network-version specific RAV creation pub trait CreateRav: NetworkVersion { - /// This might seem weird at first glance since [Horizon] and [Legacy] implementation have the same - /// function signature and don't require &self. The reason is that we can not match over T to get - /// all variants because T is a trait and not an enum. fn create_rav( - allocation_id: Address, + id: Address, signer_wallet: PrivateKeySigner, timestamp_ns: u64, value_aggregate: u128, @@ -293,7 +290,9 @@ impl CreateRav for Horizon { timestamp_ns: u64, value_aggregate: u128, ) -> Eip712SignedMessage { - create_rav_v2(allocation_id, signer_wallet, timestamp_ns, value_aggregate) + use thegraph_core::CollectionId; + let collection_id = *CollectionId::from(allocation_id); + create_rav_v2(collection_id, signer_wallet, timestamp_ns, value_aggregate) } } @@ -318,7 +317,7 @@ pub fn create_rav( /// Fixture to generate a RAV using the wallet from `keys()` pub fn create_rav_v2( - allocation_id: Address, + collection_id: FixedBytes<32>, signer_wallet: PrivateKeySigner, timestamp_ns: u64, value_aggregate: u128, @@ -326,11 +325,11 @@ pub fn create_rav_v2( Eip712SignedMessage::new( &TAP_EIP712_DOMAIN_SEPARATOR, tap_graph::v2::ReceiptAggregateVoucher { - allocationId: allocation_id, + collectionId: collection_id, timestampNs: timestamp_ns, valueAggregate: value_aggregate, payer: SENDER.1, - dataService: Address::ZERO, + dataService: INDEXER.1, // Use the same indexer address as the context serviceProvider: INDEXER.1, metadata: Bytes::new(), }, @@ -339,13 +338,12 @@ pub fn create_rav_v2( .unwrap() } -/// Generic implementation of create_received_receipt +/// Network-version specific receipt creation pub trait CreateReceipt { - /// This might seem weird at first glance since [Horizon] and [Legacy] implementation have the same - /// function signature and don't require &self. The reason is that we can not match over T to get - /// all variants because T is a trait and not an enum. + type Id: Clone + std::fmt::Debug; + fn create_received_receipt( - allocation_id: Address, + id: Self::Id, signer_wallet: &PrivateKeySigner, nonce: u64, timestamp_ns: u64, @@ -354,20 +352,24 @@ pub trait CreateReceipt { } impl CreateReceipt for Horizon { + type Id = Address; + fn create_received_receipt( - allocation_id: Address, + allocation_id: Self::Id, signer_wallet: &PrivateKeySigner, nonce: u64, timestamp_ns: u64, value: u128, ) -> CheckingReceipt { + use thegraph_core::CollectionId; + let collection_id = *CollectionId::from(allocation_id); let receipt = Eip712SignedMessage::new( &TAP_EIP712_DOMAIN_SEPARATOR, tap_graph::v2::Receipt { - allocation_id, + collection_id, payer: SENDER.1, service_provider: INDEXER.1, - data_service: Address::ZERO, + data_service: INDEXER.1, // Use the same indexer address as the context nonce, timestamp_ns, value, @@ -380,8 +382,10 @@ impl CreateReceipt for Horizon { } impl CreateReceipt for Legacy { + type Id = Address; + fn create_received_receipt( - allocation_id: Address, + allocation_id: Self::Id, signer_wallet: &PrivateKeySigner, nonce: u64, timestamp_ns: u64, @@ -480,7 +484,7 @@ pub async fn store_receipt_v2( INSERT INTO tap_horizon_receipts ( signer_address, signature, - allocation_id, + collection_id, payer, data_service, service_provider, @@ -492,7 +496,7 @@ pub async fn store_receipt_v2( "#, signer, encoded_signature, - signed_receipt.message.allocation_id.encode_hex(), + signed_receipt.message.collection_id.encode_hex(), signed_receipt.message.payer.encode_hex(), signed_receipt.message.data_service.encode_hex(), signed_receipt.message.service_provider.encode_hex(), @@ -521,21 +525,38 @@ pub async fn store_batch_receipts( let mut values = Vec::with_capacity(receipts_len); for receipt in receipts { - let receipt = match receipt.signed_receipt() { - TapReceipt::V1(receipt) => receipt, - TapReceipt::V2(_) => unimplemented!("V2 receipts not supported"), + match receipt.signed_receipt() { + TapReceipt::V1(receipt) => { + signers.push( + receipt + .recover_signer(&TAP_EIP712_DOMAIN_SEPARATOR) + .unwrap() + .encode_hex(), + ); + signatures.push(receipt.signature.as_bytes().to_vec()); + allocation_ids.push(receipt.message.allocation_id.encode_hex().to_string()); + timestamps.push(BigDecimal::from(receipt.message.timestamp_ns)); + nonces.push(BigDecimal::from(receipt.message.nonce)); + values.push(BigDecimal::from(receipt.message.value)); + } + TapReceipt::V2(receipt) => { + use thegraph_core::CollectionId; + // For V2, store collection_id in the allocation_id field (as per the database reuse strategy) + let collection_id_as_allocation = + CollectionId::from(receipt.message.collection_id).as_address(); + signers.push( + receipt + .recover_signer(&TAP_EIP712_DOMAIN_SEPARATOR) + .unwrap() + .encode_hex(), + ); + signatures.push(receipt.signature.as_bytes().to_vec()); + allocation_ids.push(collection_id_as_allocation.encode_hex().to_string()); + timestamps.push(BigDecimal::from(receipt.message.timestamp_ns)); + nonces.push(BigDecimal::from(receipt.message.nonce)); + values.push(BigDecimal::from(receipt.message.value)); + } }; - signers.push( - receipt - .recover_signer(&TAP_EIP712_DOMAIN_SEPARATOR) - .unwrap() - .encode_hex(), - ); - signatures.push(receipt.signature.as_bytes().to_vec()); - allocation_ids.push(receipt.message.allocation_id.encode_hex().to_string()); - timestamps.push(BigDecimal::from(receipt.message.timestamp_ns)); - nonces.push(BigDecimal::from(receipt.message.nonce)); - values.push(BigDecimal::from(receipt.message.value)); } let _ = sqlx::query!( r#"INSERT INTO scalar_tap_receipts ( @@ -575,7 +596,7 @@ pub async fn store_invalid_receipt( ) -> anyhow::Result { match signed_receipt { TapReceipt::V1(signed_receipt) => store_invalid_receipt_v1(pgpool, signed_receipt).await, - TapReceipt::V2(_) => unimplemented!("V2 not supported"), + TapReceipt::V2(signed_receipt) => store_invalid_receipt_v2(pgpool, signed_receipt).await, } } @@ -609,6 +630,41 @@ pub async fn store_invalid_receipt_v1( Ok(id) } +pub async fn store_invalid_receipt_v2( + pgpool: &PgPool, + signed_receipt: &tap_graph::v2::SignedReceipt, +) -> anyhow::Result { + use thegraph_core::CollectionId; + let encoded_signature = signed_receipt.signature.as_bytes().to_vec(); + + // Store collection_id in allocation_id field (database reuse strategy) + let collection_id_as_allocation = + CollectionId::from(signed_receipt.message.collection_id).as_address(); + + let record = sqlx::query!( + r#" + INSERT INTO scalar_tap_receipts_invalid (signer_address, signature, allocation_id, timestamp_ns, nonce, value) + VALUES ($1, $2, $3, $4, $5, $6) + RETURNING id + "#, + signed_receipt + .recover_signer(&TAP_EIP712_DOMAIN_SEPARATOR) + .unwrap() + .encode_hex(), + encoded_signature, + collection_id_as_allocation.encode_hex(), + BigDecimal::from(signed_receipt.message.timestamp_ns), + BigDecimal::from(signed_receipt.message.nonce), + BigDecimal::from(BigInt::from(signed_receipt.message.value)), + ) + .fetch_one(pgpool) + .await?; + + // id is BIGSERIAL, so it should be safe to cast to u64. + let id: u64 = record.id.try_into()?; + Ok(id) +} + /// Fixture to generate a wallet and address pub fn wallet(index: u32) -> (PrivateKeySigner, Address) { let wallet: PrivateKeySigner= MnemonicBuilder::::default() @@ -700,13 +756,13 @@ pub mod actors { use ractor::{Actor, ActorProcessingErr, ActorRef, SupervisionEvent}; use test_assets::{ALLOCATION_ID_0, TAP_SIGNER}; - use thegraph_core::alloy::primitives::Address; + use thegraph_core::{alloy::primitives::Address, AllocationId as AllocationIdCore}; use tokio::sync::{mpsc, watch, Notify}; use super::create_rav; use crate::agent::{ sender_account::{ReceiptFees, SenderAccountMessage}, - sender_accounts_manager::NewReceiptNotification, + sender_accounts_manager::{AllocationId, NewReceiptNotification}, sender_allocation::SenderAllocationMessage, unaggregated_receipts::UnaggregatedReceipts, }; @@ -928,7 +984,7 @@ pub mod actors { *self.next_rav_value.borrow(), ); sender_account.cast(SenderAccountMessage::UpdateReceiptFees( - ALLOCATION_ID_0, + AllocationId::Legacy(AllocationIdCore::from(ALLOCATION_ID_0)), ReceiptFees::RavRequestResponse( UnaggregatedReceipts { value: *self.next_unaggregated_fees_value.borrow(), diff --git a/crates/tap-agent/tests/sender_account_manager_test.rs b/crates/tap-agent/tests/sender_account_manager_test.rs index d644dbfd0..841e31328 100644 --- a/crates/tap-agent/tests/sender_account_manager_test.rs +++ b/crates/tap-agent/tests/sender_account_manager_test.rs @@ -19,7 +19,7 @@ use ractor::{ActorRef, ActorStatus}; use serde_json::json; use sqlx::PgPool; use test_assets::{assert_while_retry, flush_messages, TAP_SENDER as SENDER, TAP_SIGNER as SIGNER}; -use thegraph_core::alloy::primitives::U256; +use thegraph_core::{alloy::primitives::U256, AllocationId as AllocationIdCore}; use wiremock::{ matchers::{body_string_contains, method}, Mock, MockServer, ResponseTemplate, @@ -106,9 +106,11 @@ async fn sender_account_manager_layer_test(pgpool: PgPool) { .clone() .unwrap() .cast(SenderAccountMessage::UpdateAllocationIds( - vec![AllocationId::Legacy(ALLOCATION_ID_0)] - .into_iter() - .collect(), + vec![AllocationId::Legacy(AllocationIdCore::from( + ALLOCATION_ID_0, + ))] + .into_iter() + .collect(), )) .unwrap(); diff --git a/crates/tap-agent/tests/sender_account_test.rs b/crates/tap-agent/tests/sender_account_test.rs index 3a13bbed0..1b41a26b9 100644 --- a/crates/tap-agent/tests/sender_account_test.rs +++ b/crates/tap-agent/tests/sender_account_test.rs @@ -11,7 +11,7 @@ use ractor::concurrency::Duration; use serde_json::json; use sqlx::PgPool; use test_assets::{ALLOCATION_ID_0, TAP_SIGNER as SIGNER}; -use thegraph_core::alloy::hex::ToHexExt; +use thegraph_core::{alloy::hex::ToHexExt, AllocationId as AllocationIdCore}; use wiremock::{ matchers::{body_string_contains, method}, Mock, MockServer, ResponseTemplate, @@ -48,7 +48,9 @@ async fn sender_account_layer_test(pgpool: PgPool) { .await; // we expect it to create a sender allocation - let allocation_ids = HashSet::from_iter([AllocationId::Legacy(ALLOCATION_ID_0)]); + let allocation_ids = HashSet::from_iter([AllocationId::Legacy(AllocationIdCore::from( + ALLOCATION_ID_0, + ))]); sender_account .cast(SenderAccountMessage::UpdateAllocationIds( allocation_ids.clone(), diff --git a/crates/test-assets/src/lib.rs b/crates/test-assets/src/lib.rs index 729569170..b2325f5ae 100644 --- a/crates/test-assets/src/lib.rs +++ b/crates/test-assets/src/lib.rs @@ -21,7 +21,7 @@ use thegraph_core::{ signers::local::{coins_bip39::English, MnemonicBuilder, PrivateKeySigner}, sol_types::Eip712Domain, }, - deployment_id, DeploymentId, + collection_id, deployment_id, CollectionId, DeploymentId, }; use tokio::sync::mpsc; @@ -113,6 +113,50 @@ pub const ESCROW_QUERY_RESPONSE: &str = r#" } "#; +pub const ESCROW_QUERY_RESPONSE_V2: &str = r#" + { + "data": { + "paymentsEscrowAccounts": [ + { + "balance": "34", + "totalAmountThawing": "10", + "payer": { + "id": "0x9858EfFD232B4033E47d90003D41EC34EcaEda94", + "signers": [ + { + "id": "0x533661F0fb14d2E8B26223C86a610Dd7D2260892" + }, + { + "id": "0x2740f6fA9188cF53ffB6729DDD21575721dE92ce" + } + ] + } + }, + { + "balance": "42", + "totalAmountThawing": "0", + "payer": { + "id": "0x22d491bde2303f2f43325b2108d26f1eaba1e32b", + "signers": [ + { + "id": "0x245059163ff6ee14279aa7b35ea8f0fdb967df6e" + } + ] + } + }, + { + "balance": "2987", + "totalAmountThawing": "12", + "payer": { + "id": "0x192c3B6e0184Fa0Cc5B9D2bDDEb6B79Fb216a002", + "signers": [] + } + } + ] + } + } +"#; + pub const NETWORK_SUBGRAPH_DEPLOYMENT: DeploymentId = deployment_id!("QmU7zqJyHSyUP3yFii8sBtHT8FaJn2WmUnRvwjAUTjwMBP"); @@ -124,6 +168,8 @@ pub const INDEXER_ADDRESS: Address = address!("d75c4dbcb215a6cf9097cfbcc70aab259 pub const DISPUTE_MANAGER_ADDRESS: Address = address!("deadbeefcafebabedeadbeefcafebabedeadbeef"); pub const ALLOCATION_ID_0: Address = address!("fa44c72b753a66591f241c7dc04e8178c30e13af"); +pub const COLLECTION_ID_0: CollectionId = + collection_id!("000000000000000000000000fa44c72b753a66591f241c7dc04e8178c30e13af"); pub const ALLOCATION_ID_1: Address = address!("dd975e30aafebb143e54d215db8a3e8fd916a701"); @@ -369,7 +415,7 @@ pub async fn create_signed_receipt( /// Function to generate a signed receipt using the TAP_SIGNER wallet. #[bon::builder] pub async fn create_signed_receipt_v2( - #[builder(default = ALLOCATION_ID_0)] allocation_id: Address, + #[builder(default = COLLECTION_ID_0)] collection_id: CollectionId, #[builder(default)] nonce: u64, #[builder(default = SystemTime::now() .duration_since(UNIX_EPOCH) @@ -386,7 +432,7 @@ pub async fn create_signed_receipt_v2( payer: TAP_SENDER.1, service_provider: INDEXER_ADDRESS, data_service: Address::ZERO, - allocation_id, + collection_id: collection_id.into_inner(), nonce, timestamp_ns, value, @@ -415,8 +461,14 @@ pub fn pgpool() -> Pin>> { ConnectOptions, Connection, }; Box::pin(async { + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos(); + let test_path = + Box::leak(format!("{}_{}", stdext::function_name!(), timestamp).into_boxed_str()); let args = TestArgs { - test_path: stdext::function_name!(), + test_path, migrator: Some(&migrate!("../../migrations")), fixtures: &[], }; diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 9fa9d908e..08e5dbc8f 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -25,3 +25,6 @@ rand.workspace = true indexer-receipt = { path = "../crates/indexer-receipt" } num_cpus = "1.16.0" clap = { version = "4.0", features = ["derive"] } +base64 = { workspace = true } +prost = { workspace = true } +tap_aggregator = { workspace = true } diff --git a/integration-tests/fund_escrow.sh b/integration-tests/fund_escrow.sh index bdc7d2134..32468395a 100755 --- a/integration-tests/fund_escrow.sh +++ b/integration-tests/fund_escrow.sh @@ -1,41 +1,213 @@ #!/bin/bash set -e -# Constants taken from local-network/.env -GRAPH_TOKEN="0x3Aa5ebB10DC797CAC828524e59A333d0A371443c" -TAP_ESCROW="0x0355B7B8cb128fA5692729Ab3AAa199C1753f726" -# Sender address is also known as ACCOUNT0_ADDRESS -# in our services configuration along with local-network -SENDER_ADDRESS="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" -# Sender key is the private key use to sign receipts -# This is defined as ACCOUNT0_SECRET in local-network/.env -SENDER_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" -AMOUNT="10000000000000000000" - -echo "Funding escrow for sender: $SENDER_ADDRESS" - -# Approve GRT for escrow -echo "Approving GRT..." +# ============================================================================== +# FUND ESCROW FOR BOTH V1 AND V2 (HORIZON) +# ============================================================================== +# This script funds both TAP escrow contracts: +# - V1: TAPEscrow for legacy receipts +# - V2: PaymentsEscrow for Horizon receipts +# ============================================================================== + +# Function to get contract address from JSON file +get_contract_address() { + local file="$1" + local contract="$2" + if [ ! -f "$file" ]; then + echo "Error: File $file not found" + exit 1 + fi + local address=$(jq -r ".\"1337\".$contract.address" "$file") + if [ "$address" == "null" ] || [ -z "$address" ]; then + echo "Error: Could not find $contract address in $file" + exit 1 + fi + echo "$address" +} + +# Load environment variables +if [ -f ".env" ]; then + source .env +else + echo "Error: .env file not found. Please run from local-network directory." + exit 1 +fi + +# Get contract addresses +GRAPH_TOKEN=$(get_contract_address "horizon.json" "L2GraphToken") +TAP_ESCROW_V1=$(get_contract_address "tap-contracts.json" "TAPEscrow") +PAYMENTS_ESCROW_V2=$(get_contract_address "horizon.json" "PaymentsEscrow") +GRAPH_TALLY_COLLECTOR_V2=$(get_contract_address "horizon.json" "GraphTallyCollector") + +# Use environment variables from .env +SENDER_ADDRESS="$ACCOUNT0_ADDRESS" +SENDER_KEY="$ACCOUNT0_SECRET" +AMOUNT="10000000000000000000" # 10 GRT per escrow + +echo "============ FUNDING BOTH V1 AND V2 ESCROWS ============" +echo "L2GraphToken address: $GRAPH_TOKEN" +echo "TAPEscrow (v1) address: $TAP_ESCROW_V1" +echo "PaymentsEscrow (v2) address: $PAYMENTS_ESCROW_V2" +echo "GraphTallyCollector (v2) address: $GRAPH_TALLY_COLLECTOR_V2" +echo "Sender address: $SENDER_ADDRESS" +echo "Amount per escrow: $AMOUNT (10 GRT)" +echo "======================================================" + +# Check if contracts have code deployed +echo "Verifying L2GraphToken contract..." +code=$(docker exec chain cast code $GRAPH_TOKEN --rpc-url http://localhost:8545) +if [ -z "$code" ] || [ "$code" == "0x" ]; then + echo "Error: L2GraphToken contract has no code at $GRAPH_TOKEN" + exit 1 +fi + +echo "Verifying TAPEscrow (v1) contract..." +code=$(docker exec chain cast code $TAP_ESCROW_V1 --rpc-url http://localhost:8545) +if [ -z "$code" ] || [ "$code" == "0x" ]; then + echo "Error: TAPEscrow contract has no code at $TAP_ESCROW_V1" + exit 1 +fi + +echo "Verifying PaymentsEscrow (v2) contract..." +code=$(docker exec chain cast code $PAYMENTS_ESCROW_V2 --rpc-url http://localhost:8545) +if [ -z "$code" ] || [ "$code" == "0x" ]; then + echo "Error: PaymentsEscrow contract has no code at $PAYMENTS_ESCROW_V2" + exit 1 +fi + +# ============ FUND V1 ESCROW ============ +echo "" +echo "========== FUNDING V1 ESCROW ==========" + +# Check current escrow balance before funding +echo "Checking current V1 escrow balance..." +CURRENT_BALANCE_V1=$(docker exec chain cast call \ + --rpc-url http://localhost:8545 \ + $TAP_ESCROW_V1 "getEscrowAmount(address,address)(uint256)" $SENDER_ADDRESS $SENDER_ADDRESS) +echo "Current V1 escrow balance: $CURRENT_BALANCE_V1" + +# Approve GRT for V1 escrow +echo "Approving GRT for V1 escrow..." docker exec chain cast send \ --rpc-url http://localhost:8545 \ --private-key $SENDER_KEY \ - $GRAPH_TOKEN "approve(address,uint256)" $TAP_ESCROW $AMOUNT + --confirmations 1 \ + $GRAPH_TOKEN "approve(address,uint256)" $TAP_ESCROW_V1 $AMOUNT -# Deposit to escrow -echo "Depositing to escrow..." +# Deposit to V1 escrow +echo "Depositing to V1 escrow..." docker exec chain cast send \ --rpc-url http://localhost:8545 \ --private-key $SENDER_KEY \ - $TAP_ESCROW "deposit(address,uint256)" $SENDER_ADDRESS $AMOUNT + --confirmations 1 \ + $TAP_ESCROW_V1 "deposit(address,uint256)" $SENDER_ADDRESS $AMOUNT -# Verify deposit -echo "Verifying deposit..." -ESCROW_BALANCE=$(docker exec chain cast call \ +# Verify V1 deposit +echo "Verifying V1 deposit..." +ESCROW_BALANCE_V1=$(docker exec chain cast call \ --rpc-url http://localhost:8545 \ - $TAP_ESCROW "getEscrowAmount(address,address)(uint256)" $SENDER_ADDRESS $SENDER_ADDRESS) -echo "Escrow balance: $ESCROW_BALANCE" -if [[ "$ESCROW_BALANCE" == "0" ]]; then - echo "Error: Failed to fund escrow" + $TAP_ESCROW_V1 "getEscrowAmount(address,address)(uint256)" $SENDER_ADDRESS $SENDER_ADDRESS) +echo "New V1 escrow balance: $ESCROW_BALANCE_V1" + +# Check if V1 escrow balance increased +if [[ "$ESCROW_BALANCE_V1" != "0" ]] && [[ "$ESCROW_BALANCE_V1" != "$CURRENT_BALANCE_V1" ]]; then + echo "✅ Successfully funded V1 escrow!" + echo " Previous balance: $CURRENT_BALANCE_V1" + echo " Added amount: $AMOUNT" + echo " New balance: $ESCROW_BALANCE_V1" +else + echo "❌ Failed to fund V1 escrow. Balance did not increase." + echo " Current balance: $ESCROW_BALANCE_V1" exit 1 fi -echo "Successfully funded escrow" + +# ============ FUND V2 ESCROW ============ +echo "" +echo "========== FUNDING V2 ESCROW ==========" + +# For V2, we need to specify payer, collector, and receiver +# Payer is the test account, but receiver must be the indexer address +PAYER=$SENDER_ADDRESS +COLLECTOR=$SENDER_ADDRESS +RECEIVER="0xf4EF6650E48d099a4972ea5B414daB86e1998Bd3" # This must be the indexer address + +# Check current V2 escrow balance before funding +echo "Checking current V2 escrow balance..." +echo " Payer: $PAYER" +echo " Collector: $COLLECTOR" +echo " Receiver: $RECEIVER" + +# Try to get balance - V2 might use a different function name +CURRENT_BALANCE_V2="0" +echo "Current V2 escrow balance: $CURRENT_BALANCE_V2 (assuming 0 for new escrow)" + +# Approve GRT for V2 escrow +echo "Approving GRT for V2 escrow..." +docker exec chain cast send \ + --rpc-url http://localhost:8545 \ + --private-key $SENDER_KEY \ + --confirmations 1 \ + $GRAPH_TOKEN "approve(address,uint256)" $PAYMENTS_ESCROW_V2 $AMOUNT + +# For V2, we also need to authorize the signer +echo "Authorizing signer for V2..." +# Create authorization proof: payer authorizes signer (same address in test) +PROOF_DEADLINE=$(($(date +%s) + 3600)) # 1 hour from now +echo "Creating authorization proof with deadline: $PROOF_DEADLINE" + +# Create the message to sign according to _verifyAuthorizationProof +# abi.encodePacked(chainId, contractAddress, "authorizeSignerProof", deadline, authorizer) +CHAIN_ID_HEX=$(printf "%064x" 1337) # uint256: 32 bytes +CONTRACT_HEX=${GRAPH_TALLY_COLLECTOR_V2:2} # address: 20 bytes (remove 0x) +DOMAIN_HEX=$(echo -n "authorizeSignerProof" | xxd -p) # string: no length prefix +DEADLINE_HEX=$(printf "%064x" $PROOF_DEADLINE) # uint256: 32 bytes +AUTHORIZER_HEX=${SENDER_ADDRESS:2} # address: 20 bytes (remove 0x) + +MESSAGE_DATA="${CHAIN_ID_HEX}${CONTRACT_HEX}${DOMAIN_HEX}${DEADLINE_HEX}${AUTHORIZER_HEX}" +MESSAGE_HASH=$(docker exec chain cast keccak "0x$MESSAGE_DATA") + +# Sign the message with the signer's private key +PROOF=$(docker exec chain cast wallet sign --private-key $SENDER_KEY "$MESSAGE_HASH") + +echo "Calling authorizeSigner with proof..." +docker exec chain cast send \ + --rpc-url http://localhost:8545 \ + --private-key $SENDER_KEY \ + --confirmations 1 \ + $GRAPH_TALLY_COLLECTOR_V2 "authorizeSigner(address,uint256,bytes)" $SENDER_ADDRESS $PROOF_DEADLINE $PROOF 2>/dev/null || { + echo "⚠️ Signer authorization failed (likely already authorized)" + echo "Checking if signer is already authorized..." + IS_AUTHORIZED=$(docker exec chain cast call \ + --rpc-url http://localhost:8545 \ + $GRAPH_TALLY_COLLECTOR_V2 "isAuthorized(address,address)(bool)" $SENDER_ADDRESS $SENDER_ADDRESS) + if [ "$IS_AUTHORIZED" = "true" ]; then + echo "✅ Signer is already authorized" + else + echo "❌ Signer authorization failed for unknown reason" + exit 1 + fi +} + +# Deposit to V2 escrow with payer, collector, receiver +echo "Depositing to V2 escrow..." +docker exec chain cast send \ + --rpc-url http://localhost:8545 \ + --private-key $SENDER_KEY \ + --confirmations 1 \ + $PAYMENTS_ESCROW_V2 "deposit(address,address,uint256)" $COLLECTOR $RECEIVER $AMOUNT + +# Note: We'll check via the subgraph instead of direct contract call +echo "Deposit transaction completed." +ESCROW_BALANCE_V2="(check via subgraph)" + +# Since we can't easily check balance via contract call, we'll verify via transaction success +echo "✅ V2 escrow deposit transaction completed!" +echo " Payer: $PAYER" +echo " Collector: $COLLECTOR" +echo " Receiver: $RECEIVER" +echo " Amount: $AMOUNT" +echo "" +echo "Note: V2 escrow balance can be verified via the TAP V2 subgraph" + +echo "" +echo "✅ Successfully funded both V1 and V2 escrows!" \ No newline at end of file diff --git a/integration-tests/src/constants.rs b/integration-tests/src/constants.rs index df532f407..753937cdb 100644 --- a/integration-tests/src/constants.rs +++ b/integration-tests/src/constants.rs @@ -16,7 +16,7 @@ pub const TAP_AGENT_METRICS_URL: &str = "http://localhost:7300/metrics"; // which must be part of the eip712 domain // and the signing key account0_secret // they must match otherwise receipts would be rejected -pub const TAP_VERIFIER_CONTRACT: &str = "0x8198f5d8F8CfFE8f9C413d98a0A55aEB8ab9FbB7"; +pub const TAP_VERIFIER_CONTRACT: &str = "0xC9a43158891282A2B1475592D5719c001986Aaec"; pub const ACCOUNT0_SECRET: &str = "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; pub const CHAIN_ID: u64 = 1337; @@ -29,3 +29,7 @@ pub const GRT_DECIMALS: u8 = 18; pub const GRT_BASE: u128 = 10u128.pow(GRT_DECIMALS as u32); pub const MAX_RECEIPT_VALUE: u128 = GRT_BASE / 10_000; + +// Data service address for V2 testing +// For testing, we'll use the indexer address as the data service +pub const TEST_DATA_SERVICE: &str = "0xf4ef6650e48d099a4972ea5b414dab86e1998bd3"; // indexer address diff --git a/integration-tests/src/load_test.rs b/integration-tests/src/load_test.rs index 76b123181..cd017e76f 100644 --- a/integration-tests/src/load_test.rs +++ b/integration-tests/src/load_test.rs @@ -1,22 +1,24 @@ // Copyright 2023-, Edge & Node, GraphOps, and Semiotic Labs. // SPDX-License-Identifier: Apache-2.0 +use std::{str::FromStr, sync::Arc}; + use anyhow::Result; use reqwest::Client; use serde_json::json; -use std::str::FromStr; -use std::sync::Arc; -use thegraph_core::alloy::primitives::Address; -use thegraph_core::alloy::signers::local::PrivateKeySigner; -use tokio::sync::Semaphore; -use tokio::task; -use tokio::time::Instant; - -use crate::constants::{ - ACCOUNT0_SECRET, CHAIN_ID, GRAPH_URL, INDEXER_URL, MAX_RECEIPT_VALUE, SUBGRAPH_ID, - TAP_VERIFIER_CONTRACT, +use thegraph_core::alloy::{primitives::Address, signers::local::PrivateKeySigner}; +use tokio::{sync::Semaphore, task, time::Instant}; + +use crate::{ + constants::{ + ACCOUNT0_SECRET, CHAIN_ID, GRAPH_URL, INDEXER_URL, MAX_RECEIPT_VALUE, SUBGRAPH_ID, + TAP_VERIFIER_CONTRACT, + }, + utils::{ + create_request, create_tap_receipt, create_tap_receipt_v2, encode_v2_receipt, + find_allocation, + }, }; -use crate::utils::{create_request, create_tap_receipt, find_allocation}; // Function to test indexer service component // which is in charge of validating receipt signature, @@ -127,3 +129,119 @@ async fn create_and_send_receipts( Ok(()) } + +// V2 version of the load test using Horizon (V2) receipts with collection_id +pub async fn receipt_handler_load_test_v2(num_receipts: usize, concurrency: usize) -> Result<()> { + let wallet: PrivateKeySigner = ACCOUNT0_SECRET.parse().unwrap(); + + // Setup HTTP client + let http_client = Arc::new(Client::new()); + + // Query the network subgraph to find active allocations + let allocation_id = find_allocation(http_client.clone(), GRAPH_URL).await?; + let allocation_id = Address::from_str(&allocation_id)?; + + // For V2, we need payer and service provider addresses + let payer = wallet.address(); + let service_provider = allocation_id; // Using allocation_id as service provider for simplicity + + let start = Instant::now(); + let semaphore = Arc::new(Semaphore::new(concurrency)); + let mut handles = vec![]; + + for _ in 0..num_receipts { + let signer = wallet.clone(); + let client = http_client.clone(); + + let permit = semaphore.clone().acquire_owned().await.unwrap(); + let handle = task::spawn(async move { + let res = + create_and_send_receipts_v2(allocation_id, signer, client, payer, service_provider) + .await; + drop(permit); + res + }); + handles.push(handle); + } + + let mut successful_sends = 0; + let mut failed_sends = 0; + + for (index, handle) in handles.into_iter().enumerate() { + match handle.await { + Ok(send_result) => { + // Check if the send was Ok + if let Err(e) = send_result { + failed_sends += 1; + eprintln!("V2 Receipt {index} failed to send: {e:?}"); // Log the specific error + } else { + successful_sends += 1; + } + } + Err(join_error) => { + // The task panicked or was cancelled + failed_sends += 1; + eprintln!("V2 Receipt {index} task execution failed (e.g., panic): {join_error:?}"); + } + } + } + + let duration = start.elapsed(); + println!("Completed processing {num_receipts} V2 requests in {duration:?}"); + if num_receipts > 0 { + println!( + "Average time per V2 request: {:?}", + duration / num_receipts as u32 + ); + } + println!("Successfully sent V2 receipts: {successful_sends}"); + println!("Failed V2 receipts: {failed_sends}"); + + if failed_sends > 0 { + return Err(anyhow::anyhow!( + "V2 Load test completed with {} failures.", + failed_sends + )); + } + + Ok(()) +} + +async fn create_and_send_receipts_v2( + allocation_id: Address, + signer: PrivateKeySigner, + http_client: Arc, + payer: Address, + service_provider: Address, +) -> Result<()> { + let receipt = create_tap_receipt_v2( + MAX_RECEIPT_VALUE, + &allocation_id, + TAP_VERIFIER_CONTRACT, + CHAIN_ID, + &signer, + &payer, + &service_provider, + )?; + + let receipt_encoded = encode_v2_receipt(&receipt)?; + let response = create_request( + &http_client, + format!("{INDEXER_URL}/subgraphs/id/{SUBGRAPH_ID}").as_str(), + &receipt_encoded, + &json!({ + "query": "{ _meta { block { number } } }" + }), + ) + .send() + .await?; + + if !response.status().is_success() { + return Err(anyhow::anyhow!( + "Failed to send V2 receipt: {}", + response.text().await.unwrap_or_default() + )); + } + + Ok(()) +} diff --git a/integration-tests/src/main.rs b/integration-tests/src/main.rs index a96573884..348ce6665 100644 --- a/integration-tests/src/main.rs +++ b/integration-tests/src/main.rs @@ -9,10 +9,9 @@ mod utils; use anyhow::Result; use clap::Parser; -use load_test::receipt_handler_load_test; -pub(crate) use rav_tests::{test_invalid_chain_id, test_tap_rav_v1}; - +use load_test::{receipt_handler_load_test, receipt_handler_load_test_v2}; use metrics::MetricsChecker; +pub(crate) use rav_tests::{test_invalid_chain_id, test_tap_rav_v1, test_tap_rav_v2}; /// Main CLI parser structure #[derive(Parser, Debug)] @@ -25,6 +24,7 @@ struct Cli { #[derive(clap::Subcommand, Debug)] enum Commands { Rav1, + Rav2, #[clap(name = "load")] LoadService { @@ -32,6 +32,13 @@ enum Commands { #[clap(long, short, value_parser)] num_receipts: usize, }, + + #[clap(name = "load-v2")] + LoadServiceV2 { + // for example: --num-receipts 10000 or -n 10000 + #[clap(long, short, value_parser)] + num_receipts: usize, + }, } #[tokio::main] @@ -44,11 +51,20 @@ async fn main() -> Result<()> { test_invalid_chain_id().await?; test_tap_rav_v1().await?; } + // cargo run -- rav2 + Commands::Rav2 => { + test_tap_rav_v2().await?; + } // cargo run -- load --num-receipts 1000 Commands::LoadService { num_receipts } => { let concurrency = num_cpus::get(); receipt_handler_load_test(num_receipts, concurrency).await?; } + // cargo run -- load-v2 --num-receipts 1000 + Commands::LoadServiceV2 { num_receipts } => { + let concurrency = num_cpus::get(); + receipt_handler_load_test_v2(num_receipts, concurrency).await?; + } } Ok(()) diff --git a/integration-tests/src/rav_tests.rs b/integration-tests/src/rav_tests.rs index 5377b9cd7..179518d92 100644 --- a/integration-tests/src/rav_tests.rs +++ b/integration-tests/src/rav_tests.rs @@ -1,21 +1,24 @@ // Copyright 2023-, Edge & Node, GraphOps, and Semiotic Labs. // SPDX-License-Identifier: Apache-2.0 +use std::{str::FromStr, sync::Arc, time::Duration}; + use anyhow::Result; use reqwest::Client; use serde_json::json; -use std::str::FromStr; -use std::sync::Arc; -use std::time::Duration; -use thegraph_core::alloy::primitives::Address; -use thegraph_core::alloy::signers::local::PrivateKeySigner; - -use crate::constants::{ - ACCOUNT0_SECRET, CHAIN_ID, GATEWAY_API_KEY, GATEWAY_URL, GRAPH_URL, INDEXER_URL, - MAX_RECEIPT_VALUE, SUBGRAPH_ID, TAP_AGENT_METRICS_URL, TAP_VERIFIER_CONTRACT, +use thegraph_core::alloy::{primitives::Address, signers::local::PrivateKeySigner}; + +use crate::{ + constants::{ + ACCOUNT0_SECRET, CHAIN_ID, GATEWAY_API_KEY, GATEWAY_URL, GRAPH_URL, INDEXER_URL, + MAX_RECEIPT_VALUE, SUBGRAPH_ID, TAP_AGENT_METRICS_URL, TAP_VERIFIER_CONTRACT, + }, + utils::{ + create_request, create_tap_receipt, create_tap_receipt_v2, encode_v2_receipt, + find_allocation, + }, + MetricsChecker, }; -use crate::utils::{create_request, create_tap_receipt, find_allocation}; -use crate::MetricsChecker; const WAIT_TIME_BATCHES: u64 = 40; const NUM_RECEIPTS: u32 = 3; @@ -208,3 +211,183 @@ pub async fn test_invalid_chain_id() -> Result<()> { Ok(()) } + +// Function to test the TAP RAV generation with V2 receipts +pub async fn test_tap_rav_v2() -> Result<()> { + // Setup HTTP client + let http_client = Arc::new(Client::new()); + let wallet: PrivateKeySigner = ACCOUNT0_SECRET.parse().unwrap(); + + // Query the network subgraph to find active allocations + let allocation_id = find_allocation(http_client.clone(), GRAPH_URL).await?; + let allocation_id = Address::from_str(&allocation_id)?; + + // For V2, we need payer and service provider addresses + let payer = wallet.address(); + let service_provider = allocation_id; // Using allocation_id as service provider for simplicity + + // Create a metrics checker + let metrics_checker = + MetricsChecker::new(http_client.clone(), TAP_AGENT_METRICS_URL.to_string()); + + // First check initial metrics + let initial_metrics = metrics_checker.get_current_metrics().await?; + let initial_ravs_created = + initial_metrics.ravs_created_by_allocation(&allocation_id.to_string()); + let initial_unaggregated = + initial_metrics.unaggregated_fees_by_allocation(&allocation_id.to_string()); + + println!( + "\n=== V2 Initial metrics: RAVs created: {initial_ravs_created}, Unaggregated fees: {initial_unaggregated} ===" + ); + + println!("\n=== V2 STAGE 1: Sending large receipt batches with small pauses ==="); + + // Send multiple V2 receipts in two batches with a gap between them + let mut total_successful = 0; + + for batch in 0..BATCHES { + println!( + "Sending V2 batch {} of 2 with {} receipts each...", + batch + 1, + NUM_RECEIPTS + ); + + for i in 0..NUM_RECEIPTS { + // Create V2 receipt + let receipt = create_tap_receipt_v2( + MAX_RECEIPT_VALUE, + &allocation_id, + TAP_VERIFIER_CONTRACT, + CHAIN_ID, + &wallet, + &payer, + &service_provider, + )?; + + let receipt_encoded = encode_v2_receipt(&receipt)?; + + let response = create_request( + &http_client, + &format!("{INDEXER_URL}/subgraphs/id/{SUBGRAPH_ID}"), + &receipt_encoded, + &json!({ + "query": "{ _meta { block { number } } }" + }), + ) + .send() + .await?; + + if response.status().is_success() { + total_successful += 1; + println!( + "V2 Query {} of batch {} sent successfully", + i + 1, + batch + 1 + ); + } else { + return Err(anyhow::anyhow!( + "Failed to send V2 query: {}", + response.status() + )); + } + + // Small pause between queries within batch + tokio::time::sleep(Duration::from_millis(100)).await; + } + + // Check metrics after batch + let batch_metrics = metrics_checker.get_current_metrics().await?; + println!( + "After V2 batch {}: RAVs created: {}, Unaggregated fees: {}", + batch + 1, + batch_metrics.ravs_created_by_allocation(&allocation_id.to_string()), + batch_metrics.unaggregated_fees_by_allocation(&allocation_id.to_string()) + ); + + // Wait between batches - long enough for first batch to exit buffer + if batch < 1 { + println!("Waiting for buffer period + 5s..."); + tokio::time::sleep(Duration::from_secs(WAIT_TIME_BATCHES)).await; + } + } + + println!("\n=== V2 STAGE 2: Sending continuous trigger receipts ==="); + + // Now send a series of regular V2 queries with short intervals until RAV is detected + for i in 0..MAX_TRIGGERS { + println!("Sending V2 trigger query {}/{}...", i + 1, MAX_TRIGGERS); + + // Create V2 receipt + let receipt = create_tap_receipt_v2( + MAX_RECEIPT_VALUE / 10, // Smaller value for trigger receipts + &allocation_id, + TAP_VERIFIER_CONTRACT, + CHAIN_ID, + &wallet, + &payer, + &service_provider, + )?; + + let receipt_encoded = encode_v2_receipt(&receipt)?; + + let response = create_request( + &http_client, + &format!("{INDEXER_URL}/subgraphs/id/{SUBGRAPH_ID}"), + &receipt_encoded, + &json!({ + "query": "{ _meta { block { number } } }" + }), + ) + .send() + .await?; + + if response.status().is_success() { + total_successful += 1; + println!("V2 Trigger receipt {} sent successfully", i + 1); + } else { + return Err(anyhow::anyhow!( + "Failed to send V2 trigger query: {}", + response.status() + )); + } + + // Check after each trigger + tokio::time::sleep(Duration::from_secs(1)).await; + + let current_metrics = metrics_checker.get_current_metrics().await?; + let current_ravs_created = + current_metrics.ravs_created_by_allocation(&allocation_id.to_string()); + let current_unaggregated = + current_metrics.unaggregated_fees_by_allocation(&allocation_id.to_string()); + + println!( + "After V2 trigger {}: RAVs created: {}, Unaggregated fees: {}", + i + 1, + current_ravs_created, + current_unaggregated + ); + + // If we've succeeded, exit early + if current_ravs_created > initial_ravs_created { + println!( + "✅ V2 TEST PASSED: RAVs created increased from {initial_ravs_created} to {current_ravs_created}!" + ); + return Ok(()); + } + + if current_unaggregated < initial_unaggregated * 0.9 { + println!( + "✅ V2 TEST PASSED: Unaggregated fees decreased significantly from {initial_unaggregated} to {current_unaggregated}!" + ); + return Ok(()); + } + } + + println!("\n=== V2 Summary ==="); + println!("Total V2 queries sent successfully: {total_successful}"); + + // If we got here, test failed + println!("❌ V2 TEST FAILED: No RAV generation detected"); + Err(anyhow::anyhow!("Failed to detect V2 RAV generation")) +} diff --git a/integration-tests/src/utils.rs b/integration-tests/src/utils.rs index 447518f18..4936bfec1 100644 --- a/integration-tests/src/utils.rs +++ b/integration-tests/src/utils.rs @@ -1,16 +1,25 @@ // Copyright 2023-, Edge & Node, GraphOps, and Semiotic Labs. // SPDX-License-Identifier: Apache-2.0 -use rand::{rng, Rng}; -use serde_json::json; -use std::time::{Duration, SystemTime}; -use std::{str::FromStr, sync::Arc}; +use std::{ + str::FromStr, + sync::Arc, + time::{Duration, SystemTime}, +}; use anyhow::Result; +use base64::prelude::*; +use prost::Message; +use rand::{rng, Rng}; use reqwest::Client; +use serde_json::json; +use tap_aggregator::grpc; use tap_core::{signed_message::Eip712SignedMessage, tap_eip712_domain}; use tap_graph::Receipt; use thegraph_core::alloy::{primitives::Address, signers::local::PrivateKeySigner}; +use thegraph_core::CollectionId; + +use crate::constants::TEST_DATA_SERVICE; pub fn create_tap_receipt( value: u128, @@ -47,6 +56,57 @@ pub fn create_tap_receipt( Ok(receipt) } +pub fn create_tap_receipt_v2( + value: u128, + allocation_id: &Address, // Used to derive collection_id in V2 + verifier_contract: &str, + chain_id: u64, + wallet: &PrivateKeySigner, + payer: &Address, + service_provider: &Address, +) -> Result> { + let nonce = rng().random::(); + + // Get timestamp in nanoseconds + let timestamp = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH)? + .as_nanos(); + let timestamp_ns = timestamp as u64; + + // In V2, convert the allocation_id to a collection_id + // For the migration period, we derive collection_id from allocation_id + let collection_id = CollectionId::from(*allocation_id); + + // Create domain separator + let eip712_domain_separator = + tap_eip712_domain(chain_id, Address::from_str(verifier_contract)?); + + // Create and sign V2 receipt + println!("Creating and signing V2 receipt..."); + let receipt = Eip712SignedMessage::new( + &eip712_domain_separator, + tap_graph::v2::Receipt { + collection_id: *collection_id, + payer: *payer, + service_provider: *service_provider, + data_service: Address::from_str(TEST_DATA_SERVICE)?, // Use proper data service + nonce, + timestamp_ns, + value, + }, + wallet, + )?; + + Ok(receipt) +} + +pub fn encode_v2_receipt(receipt: &Eip712SignedMessage) -> Result { + let protobuf_receipt = grpc::v2::SignedReceipt::from(receipt.clone()); + let encoded = protobuf_receipt.encode_to_vec(); + let base64_encoded = BASE64_STANDARD.encode(encoded); + Ok(base64_encoded) +} + // Function to create a configured request pub fn create_request( client: &reqwest::Client, diff --git a/justfile b/justfile index 26ce97468..5cc1ad991 100644 --- a/justfile +++ b/justfile @@ -109,3 +109,11 @@ profile-restore: test-local: @cd integration-tests && ./fund_escrow.sh @cd integration-tests && cargo run -- rav1 + +test-local-v2: + @cd integration-tests && ./fund_escrow.sh + @cd integration-tests && cargo run -- rav2 + +load-test-v2 num_receipts="1000": + @cd integration-tests && ./fund_escrow.sh + @cd integration-tests && cargo run -- load-v2 --num-receipts {{num_receipts}} diff --git a/migrations/20250131122241_tap_horizon_receipts.up.sql b/migrations/20250131122241_tap_horizon_receipts.up.sql index 02df77256..c442b1f9e 100644 --- a/migrations/20250131122241_tap_horizon_receipts.up.sql +++ b/migrations/20250131122241_tap_horizon_receipts.up.sql @@ -5,7 +5,7 @@ CREATE TABLE IF NOT EXISTS tap_horizon_receipts ( -- Values below are the individual fields of the EIP-712 receipt signature BYTEA NOT NULL, - allocation_id CHAR(40) NOT NULL, + collection_id CHAR(64) NOT NULL, payer CHAR(40) NOT NULL, data_service CHAR(40) NOT NULL, service_provider CHAR(40) NOT NULL, @@ -14,14 +14,14 @@ CREATE TABLE IF NOT EXISTS tap_horizon_receipts ( value NUMERIC(39) NOT NULL ); -CREATE INDEX IF NOT EXISTS tap_horizon_receipts_allocation_id_idx ON tap_horizon_receipts (allocation_id); +CREATE INDEX IF NOT EXISTS tap_horizon_receipts_collection_id_idx ON tap_horizon_receipts (collection_id); CREATE INDEX IF NOT EXISTS tap_horizon_receipts_timestamp_ns_idx ON tap_horizon_receipts (timestamp_ns); CREATE FUNCTION tap_horizon_receipt_notify() RETURNS trigger AS $$ BEGIN - PERFORM pg_notify('tap_horizon_receipt_notification', format('{"id": %s, "allocation_id": "%s", "signer_address": "%s", "timestamp_ns": %s, "value": %s}', NEW.id, NEW.allocation_id, NEW.signer_address, NEW.timestamp_ns, NEW.value)); + PERFORM pg_notify('tap_horizon_receipt_notification', format('{"id": %s, "collection_id": "%s", "signer_address": "%s", "timestamp_ns": %s, "value": %s}', NEW.id, NEW.collection_id, NEW.signer_address, NEW.timestamp_ns, NEW.value)); RETURN NEW; END; $$ LANGUAGE 'plpgsql'; @@ -39,7 +39,7 @@ CREATE TABLE IF NOT EXISTS tap_horizon_receipts_invalid ( -- Values below are the individual fields of the EIP-712 receipt signature BYTEA NOT NULL, - allocation_id CHAR(40) NOT NULL, + collection_id CHAR(64) NOT NULL, payer CHAR(40) NOT NULL, data_service CHAR(40) NOT NULL, service_provider CHAR(40) NOT NULL, diff --git a/migrations/20250210152412_tap_horizon_ravs.up.sql b/migrations/20250210152412_tap_horizon_ravs.up.sql index a59418b67..418cfdc75 100644 --- a/migrations/20250210152412_tap_horizon_ravs.up.sql +++ b/migrations/20250210152412_tap_horizon_ravs.up.sql @@ -2,7 +2,7 @@ CREATE TABLE IF NOT EXISTS tap_horizon_ravs ( -- Values below are the individual fields of the EIP-712 RAV signature BYTEA NOT NULL, - allocation_id CHAR(40) NOT NULL, + collection_id CHAR(64) NOT NULL, payer CHAR(40) NOT NULL, data_service CHAR(40) NOT NULL, service_provider CHAR(40) NOT NULL, @@ -12,9 +12,10 @@ CREATE TABLE IF NOT EXISTS tap_horizon_ravs ( last BOOLEAN DEFAULT FALSE NOT NULL, final BOOLEAN DEFAULT FALSE NOT NULL, - PRIMARY KEY (payer, data_service, service_provider, allocation_id), + PRIMARY KEY (payer, data_service, service_provider, collection_id), -- To make indexer-agent's sequelize happy + redeemed_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE, updated_at TIMESTAMP WITH TIME ZONE ); @@ -23,7 +24,7 @@ CREATE TABLE IF NOT EXISTS tap_horizon_ravs ( -- Used for logging and debugging purposes. CREATE TABLE IF NOT EXISTS tap_horizon_rav_requests_failed ( id BIGSERIAL PRIMARY KEY, - allocation_id CHAR(40) NOT NULL, + collection_id CHAR(64) NOT NULL, payer CHAR(40) NOT NULL, data_service CHAR(40) NOT NULL, service_provider CHAR(40) NOT NULL, diff --git a/setup-test-network.sh b/setup-test-network.sh index f5a0f11a5..20b3a1b67 100755 --- a/setup-test-network.sh +++ b/setup-test-network.sh @@ -2,9 +2,9 @@ set -e # ============================================================================== -# SETUP LOCAL GRAPH NETWORK FOR TESTING +# SETUP LOCAL GRAPH NETWORK FOR TESTING (HORIZON VERSION) # ============================================================================== -# This script sets up a local Graph network for testing. +# This script sets up a local Graph network for testing with horizon upgrade. # # NOTES: # - If you encounter container conflicts, run: docker compose down @@ -34,13 +34,8 @@ container_running() { return $? } -# Function to fund the escrow smart contract -# 1. first read .env variables from local-network/.env -# 2. then read contract addresses from local-network/contracts.json -# 3. finally, use the cast command to approve and deposit GRT to the escrow -# this should be done just after deploying the gateway -# otherwise it does not move forward in its setup process -# causing false error during deployment of our local testnet +# Function to fund the escrow smart contract for horizon +# Uses L2GraphToken and TAPEscrow from the horizon structure fund_escrow() { echo "Funding escrow for sender..." @@ -51,11 +46,14 @@ fund_escrow() { return 1 fi - GRAPH_TOKEN=$(jq -r '."1337".GraphToken.address' local-network/contracts.json) - TAP_ESCROW=$(jq -r '."1337".TAPEscrow.address' local-network/contracts.json) + # Use L2GraphToken from horizon.json for horizon upgrade + GRAPH_TOKEN=$(jq -r '."1337".L2GraphToken.address' local-network/horizon.json) + TAP_ESCROW=$(jq -r '."1337".TAPEscrow.address' local-network/tap-contracts.json) - if [ -z "$GRAPH_TOKEN" ] || [ -z "$TAP_ESCROW" ]; then - echo "Error: Could not read contract addresses from contracts.json" + if [ -z "$GRAPH_TOKEN" ] || [ -z "$TAP_ESCROW" ] || [ "$GRAPH_TOKEN" == "null" ] || [ "$TAP_ESCROW" == "null" ]; then + echo "Error: Could not read contract addresses from horizon.json or tap-contracts.json" + echo "GRAPH_TOKEN: $GRAPH_TOKEN" + echo "TAP_ESCROW: $TAP_ESCROW" return 1 fi @@ -64,7 +62,7 @@ fund_escrow() { SENDER_KEY="$ACCOUNT0_SECRET" AMOUNT="10000000000000000000" - echo "Using GraphToken at: $GRAPH_TOKEN" + echo "Using L2GraphToken at: $GRAPH_TOKEN" echo "Using TapEscrow at: $TAP_ESCROW" echo "Using sender address: $SENDER_ADDRESS" @@ -116,8 +114,8 @@ pwd if [ ! -d "local-network" ]; then git clone https://github.com/semiotic-ai/local-network.git cd local-network - # Checkout to a branch with no dipper - git checkout suchapalaver/main-no-dipper + # Checkout to the horizon branch + git checkout suchapalaver/test/horizon cd .. fi @@ -135,17 +133,17 @@ docker compose up -d graph-contracts # Wait for contracts to be deployed timeout 300 bash -c 'until docker ps -a | grep graph-contracts | grep -q "Exited (0)"; do sleep 5; done' -# Verify the contracts have code -graph_token_address=$(jq -r '."1337".GraphToken.address' contracts.json) -controller_address=$(jq -r '."1337".Controller.address' contracts.json) +# Verify the contracts have code using horizon structure +l2_graph_token_address=$(jq -r '."1337".L2GraphToken.address' horizon.json) +controller_address=$(jq -r '."1337".Controller.address' horizon.json) -echo "Checking GraphToken contract at $graph_token_address" -code=$(docker exec chain cast code $graph_token_address --rpc-url http://localhost:8545) +echo "Checking L2GraphToken contract at $l2_graph_token_address" +code=$(docker exec chain cast code $l2_graph_token_address --rpc-url http://localhost:8545) if [ -z "$code" ] || [ "$code" == "0x" ]; then - echo "ERROR: GraphToken contract has no code!" + echo "ERROR: L2GraphToken contract has no code!" exit 1 fi -echo "GraphToken contract verified." +echo "L2GraphToken contract verified." echo "Checking Controller contract at $controller_address" code=$(docker exec chain cast code $controller_address --rpc-url http://localhost:8545) @@ -233,15 +231,23 @@ timeout 30 bash -c 'until docker ps | grep indexer | grep -q healthy; do sleep 5 timeout 30 bash -c 'until docker ps | grep tap-agent | grep -q healthy; do sleep 5; done' echo "Building gateway image..." +source local-network/.env docker build -t local-gateway:latest ./local-network/gateway echo "Running gateway container..." +# Updated to use the horizon file structure docker run -d --name gateway \ --network local-network_default \ -p 7700:7700 \ - -v $(pwd)/local-network/.env:/opt/.env:ro \ - -v $(pwd)/local-network/contracts.json:/opt/contracts.json:ro \ + -v "$(pwd)/local-network/tap-contracts.json":/opt/tap-contracts.json:ro \ + -v "$(pwd)/local-network/subgraph-service.json":/opt/subgraph-service.json:ro \ -e RUST_LOG=info,graph_gateway=trace \ + -e ACCOUNT0_SECRET="$ACCOUNT0_SECRET" \ + -e ACCOUNT0_ADDRESS="$ACCOUNT0_ADDRESS" \ + -e GATEWAY_API_KEY="$GATEWAY_API_KEY" \ + -e GRAPH_NODE_GRAPHQL="$GRAPH_NODE_GRAPHQL" \ + -e REDPANDA_KAFKA="$REDPANDA_KAFKA" \ + -e INDEXER_SERVICE="$INDEXER_SERVICE" \ --restart on-failure:3 \ local-gateway:latest @@ -269,7 +275,7 @@ END_CONTAINERS_SIZE=$(docker system df --format '{{.ContainersSize}}' 2>/dev/nul END_VOLUMES_SIZE=$(docker system df --format '{{.VolumesSize}}' 2>/dev/null || echo "N/A") echo "All services are now running!" -echo "You can enjoy your new local network setup for testing." +echo "You can enjoy your new local network setup for testing with horizon upgrade." echo "============ FINAL DISK USAGE ============" echo "Docker directory usage: $END_SPACE"