1
1
// Copyright 2023-, GraphOps and Semiotic Labs.
2
2
// SPDX-License-Identifier: Apache-2.0
3
3
4
- use alloy:: hex:: ToHexExt ;
4
+ use alloy:: { dyn_abi :: Eip712Domain , hex:: ToHexExt } ;
5
5
use anyhow:: anyhow;
6
6
use bigdecimal:: num_bigint:: BigInt ;
7
- use sqlx:: types:: BigDecimal ;
7
+ use sqlx:: { types:: BigDecimal , PgPool } ;
8
8
use tap_core:: {
9
9
manager:: adapters:: ReceiptStore ,
10
10
receipt:: { state:: Checking , ReceiptWithState } ,
11
11
} ;
12
+ use tokio:: { select, sync:: mpsc:: Receiver , task:: JoinHandle } ;
13
+ use tokio_util:: sync:: CancellationToken ;
12
14
use tracing:: error;
13
15
14
16
use super :: { AdapterError , IndexerTapContext } ;
15
17
18
+ #[ derive( Clone ) ]
19
+ pub struct InnerContext {
20
+ pub pgpool : PgPool ,
21
+ }
22
+
23
+ impl InnerContext {
24
+ async fn store_receipts ( & self , receipts : Vec < DatabaseReceipt > ) -> Result < ( ) , AdapterError > {
25
+ let receipts_len = receipts. len ( ) ;
26
+ let mut signers = Vec :: with_capacity ( receipts_len) ;
27
+ let mut signatures = Vec :: with_capacity ( receipts_len) ;
28
+ let mut allocation_ids = Vec :: with_capacity ( receipts_len) ;
29
+ let mut timestamps = Vec :: with_capacity ( receipts_len) ;
30
+ let mut nonces = Vec :: with_capacity ( receipts_len) ;
31
+ let mut values = Vec :: with_capacity ( receipts_len) ;
32
+
33
+ for receipt in receipts {
34
+ signers. push ( receipt. signer_address ) ;
35
+ signatures. push ( receipt. signature ) ;
36
+ allocation_ids. push ( receipt. allocation_id ) ;
37
+ timestamps. push ( receipt. timestamp_ns ) ;
38
+ nonces. push ( receipt. nonce ) ;
39
+ values. push ( receipt. value ) ;
40
+ }
41
+ sqlx:: query!(
42
+ r#"INSERT INTO scalar_tap_receipts (
43
+ signer_address,
44
+ signature,
45
+ allocation_id,
46
+ timestamp_ns,
47
+ nonce,
48
+ value
49
+ ) SELECT * FROM UNNEST(
50
+ $1::CHAR(40)[],
51
+ $2::BYTEA[],
52
+ $3::CHAR(40)[],
53
+ $4::NUMERIC(20)[],
54
+ $5::NUMERIC(20)[],
55
+ $6::NUMERIC(40)[]
56
+ )"# ,
57
+ & signers,
58
+ & signatures,
59
+ & allocation_ids,
60
+ & timestamps,
61
+ & nonces,
62
+ & values,
63
+ )
64
+ . execute ( & self . pgpool )
65
+ . await
66
+ . map_err ( |e| {
67
+ error ! ( "Failed to store receipt: {}" , e) ;
68
+ anyhow ! ( e)
69
+ } ) ?;
70
+
71
+ Ok ( ( ) )
72
+ }
73
+ }
74
+
75
+ impl IndexerTapContext {
76
+ pub fn spawn_store_receipt_task (
77
+ inner_context : InnerContext ,
78
+ mut receiver : Receiver < DatabaseReceipt > ,
79
+ cancelation_token : CancellationToken ,
80
+ ) -> JoinHandle < ( ) > {
81
+ const BUFFER_SIZE : usize = 100 ;
82
+ tokio:: spawn ( async move {
83
+ loop {
84
+ let mut buffer = Vec :: with_capacity ( BUFFER_SIZE ) ;
85
+ select ! {
86
+ biased;
87
+ _ = receiver. recv_many( & mut buffer, BUFFER_SIZE ) => {
88
+ if let Err ( e) = inner_context. store_receipts( buffer) . await {
89
+ error!( "Failed to store receipts: {}" , e) ;
90
+ }
91
+ }
92
+ _ = cancelation_token. cancelled( ) => { break } ,
93
+ }
94
+ }
95
+ } )
96
+ }
97
+ }
98
+
16
99
#[ async_trait:: async_trait]
17
100
impl ReceiptStore for IndexerTapContext {
18
101
type AdapterError = AdapterError ;
@@ -21,38 +104,53 @@ impl ReceiptStore for IndexerTapContext {
21
104
& self ,
22
105
receipt : ReceiptWithState < Checking > ,
23
106
) -> Result < u64 , Self :: AdapterError > {
107
+ let db_receipt = DatabaseReceipt :: from_receipt ( receipt, & self . domain_separator ) ?;
108
+ self . receipt_producer . send ( db_receipt) . await . map_err ( |e| {
109
+ error ! ( "Failed to queue receipt for storage: {}" , e) ;
110
+ anyhow ! ( e)
111
+ } ) ?;
112
+
113
+ // We don't need receipt_ids
114
+ Ok ( 0 )
115
+ }
116
+ }
117
+
118
+ pub struct DatabaseReceipt {
119
+ signer_address : String ,
120
+ signature : Vec < u8 > ,
121
+ allocation_id : String ,
122
+ timestamp_ns : BigDecimal ,
123
+ nonce : BigDecimal ,
124
+ value : BigDecimal ,
125
+ }
126
+
127
+ impl DatabaseReceipt {
128
+ fn from_receipt (
129
+ receipt : ReceiptWithState < Checking > ,
130
+ separator : & Eip712Domain ,
131
+ ) -> anyhow:: Result < Self > {
24
132
let receipt = receipt. signed_receipt ( ) ;
25
- let allocation_id = receipt. message . allocation_id ;
26
- let encoded_signature = receipt. signature . as_bytes ( ) . to_vec ( ) ;
133
+ let allocation_id = receipt. message . allocation_id . encode_hex ( ) ;
134
+ let signature = receipt. signature . as_bytes ( ) . to_vec ( ) ;
27
135
28
- let receipt_signer = receipt
29
- . recover_signer ( self . domain_separator . as_ref ( ) )
136
+ let signer_address = receipt
137
+ . recover_signer ( separator )
30
138
. map_err ( |e| {
31
139
error ! ( "Failed to recover receipt signer: {}" , e) ;
32
140
anyhow ! ( e)
33
- } ) ?;
34
-
35
- // TODO: consider doing this in another async task to avoid slowing down the paid query flow.
36
- sqlx:: query!(
37
- r#"
38
- INSERT INTO scalar_tap_receipts (signer_address, signature, allocation_id, timestamp_ns, nonce, value)
39
- VALUES ($1, $2, $3, $4, $5, $6)
40
- "# ,
41
- receipt_signer. encode_hex( ) ,
42
- encoded_signature,
43
- allocation_id. encode_hex( ) ,
44
- BigDecimal :: from( receipt. message. timestamp_ns) ,
45
- BigDecimal :: from( receipt. message. nonce) ,
46
- BigDecimal :: from( BigInt :: from( receipt. message. value) ) ,
47
- )
48
- . execute ( & self . pgpool )
49
- . await
50
- . map_err ( |e| {
51
- error ! ( "Failed to store receipt: {}" , e) ;
52
- anyhow ! ( e)
53
- } ) ?;
141
+ } ) ?
142
+ . encode_hex ( ) ;
54
143
55
- // We don't need receipt_ids
56
- Ok ( 0 )
144
+ let timestamp_ns = BigDecimal :: from ( receipt. message . timestamp_ns ) ;
145
+ let nonce = BigDecimal :: from ( receipt. message . nonce ) ;
146
+ let value = BigDecimal :: from ( BigInt :: from ( receipt. message . value ) ) ;
147
+ Ok ( Self {
148
+ allocation_id,
149
+ nonce,
150
+ signature,
151
+ signer_address,
152
+ timestamp_ns,
153
+ value,
154
+ } )
57
155
}
58
156
}
0 commit comments