-
Notifications
You must be signed in to change notification settings - Fork 57
/
Copy pathblockhash.ts
155 lines (147 loc) · 6.74 KB
/
blockhash.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import { SOLANA_ERROR__TRANSACTION__EXPECTED_BLOCKHASH_LIFETIME, SolanaError } from '@solana/errors';
import { assertIsBlockhash, type Blockhash } from '@solana/rpc-types';
import { TransactionMessageWithDurableNonceLifetime } from './durable-nonce';
import { BaseTransactionMessage } from './transaction-message';
/**
* A constraint which, when applied to a transaction message, makes that transaction message
* eligible to land on the network. The transaction message will continue to be eligible to land
* until the network considers the `blockhash` to be expired.
*
* This can happen when the network proceeds past the `lastValidBlockHeight` for which the blockhash
* is considered valid, or when the network switches to a fork where that blockhash is not present.
*/
type BlockhashLifetimeConstraint = Readonly<{
/**
* A recent blockhash observed by the transaction proposer.
*
* The transaction message will be considered eligible to land until the network determines this
* blockhash to be too old, or has switched to a fork where it is not present.
*/
blockhash: Blockhash;
/**
* This is the block height beyond which the network will consider the blockhash to be too old
* to make a transaction message eligible to land.
*/
lastValidBlockHeight: bigint;
}>;
/**
* Represents a transaction message whose lifetime is defined by the age of the blockhash it
* includes.
*
* Such a transaction can only be landed on the network if the current block height of the network
* is less than or equal to the value of
* `TransactionMessageWithBlockhashLifetime['lifetimeConstraint']['lastValidBlockHeight']`.
*/
export interface TransactionMessageWithBlockhashLifetime {
readonly lifetimeConstraint: BlockhashLifetimeConstraint;
}
/**
* A type guard that returns `true` if the transaction message conforms to the
* {@link TransactionMessageWithBlockhashLifetime} type, and refines its type for use in your
* program.
*
* @example
* ```ts
* import { isTransactionMessageWithBlockhashLifetime } from '@solana/transaction-messages';
*
* if (isTransactionMessageWithBlockhashLifetime(message)) {
* // At this point, `message` has been refined to a `TransactionMessageWithBlockhashLifetime`.
* const { blockhash } = message.lifetimeConstraint;
* const { value: blockhashIsValid } = await rpc.isBlockhashValid(blockhash).send();
* setBlockhashIsValid(blockhashIsValid);
* } else {
* setError(
* `${getSignatureFromTransaction(transaction)} does not have a blockhash-based lifetime`,
* );
* }
* ```
*/
export function isTransactionMessageWithBlockhashLifetime(
transactionMessage: BaseTransactionMessage | (BaseTransactionMessage & TransactionMessageWithBlockhashLifetime),
): transactionMessage is BaseTransactionMessage & TransactionMessageWithBlockhashLifetime {
const lifetimeConstraintShapeMatches =
'lifetimeConstraint' in transactionMessage &&
typeof transactionMessage.lifetimeConstraint.blockhash === 'string' &&
typeof transactionMessage.lifetimeConstraint.lastValidBlockHeight === 'bigint';
if (!lifetimeConstraintShapeMatches) return false;
try {
assertIsBlockhash(transactionMessage.lifetimeConstraint.blockhash);
return true;
} catch {
return false;
}
}
/**
* From time to time you might acquire a transaction message, that you expect to have a
* blockhash-based lifetime, from an untrusted network API or user input. Use this function to
* assert that such a transaction message actually has a blockhash-based lifetime.
*
* @example
* ```ts
* import { assertIsTransactionMessageWithBlockhashLifetime } from '@solana/transaction-messages';
*
* try {
* // If this type assertion function doesn't throw, then
* // Typescript will upcast `message` to `TransactionMessageWithBlockhashLifetime`.
* assertIsTransactionMessageWithBlockhashLifetime(message);
* // At this point, `message` is a `TransactionMessageWithBlockhashLifetime` that can be used
* // with the RPC.
* const { blockhash } = message.lifetimeConstraint;
* const { value: blockhashIsValid } = await rpc.isBlockhashValid(blockhash).send();
* } catch (e) {
* // `message` turned out not to have a blockhash-based lifetime
* }
* ```
*/
export function assertIsTransactionMessageWithBlockhashLifetime(
transactionMessage: BaseTransactionMessage | (BaseTransactionMessage & TransactionMessageWithBlockhashLifetime),
): asserts transactionMessage is BaseTransactionMessage & TransactionMessageWithBlockhashLifetime {
if (!isTransactionMessageWithBlockhashLifetime(transactionMessage)) {
throw new SolanaError(SOLANA_ERROR__TRANSACTION__EXPECTED_BLOCKHASH_LIFETIME);
}
}
/**
* Given a blockhash and the last block height at which that blockhash is considered usable to land
* transactions, this method will return a new transaction message having the same type as the one
* supplied plus the `TransactionMessageWithBlockhashLifetime` type.
*
* @example
* ```ts
* import { setTransactionMessageLifetimeUsingBlockhash } from '@solana/transaction-messages';
*
* const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
* const txMessageWithBlockhashLifetime = setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, txMessage);
* ```
*/
export function setTransactionMessageLifetimeUsingBlockhash<
TTransactionMessage extends BaseTransactionMessage & TransactionMessageWithDurableNonceLifetime,
>(
blockhashLifetimeConstraint: BlockhashLifetimeConstraint,
transactionMessage: TTransactionMessage,
): Omit<TTransactionMessage, 'lifetimeConstraint'> & TransactionMessageWithBlockhashLifetime;
export function setTransactionMessageLifetimeUsingBlockhash<
TTransactionMessage extends
| BaseTransactionMessage
| (BaseTransactionMessage & TransactionMessageWithBlockhashLifetime),
>(
blockhashLifetimeConstraint: BlockhashLifetimeConstraint,
transactionMessage: TTransactionMessage,
): TransactionMessageWithBlockhashLifetime & TTransactionMessage;
export function setTransactionMessageLifetimeUsingBlockhash(
blockhashLifetimeConstraint: BlockhashLifetimeConstraint,
transactionMessage: BaseTransactionMessage | (BaseTransactionMessage & TransactionMessageWithBlockhashLifetime),
) {
if (
'lifetimeConstraint' in transactionMessage &&
transactionMessage.lifetimeConstraint.blockhash === blockhashLifetimeConstraint.blockhash &&
transactionMessage.lifetimeConstraint.lastValidBlockHeight === blockhashLifetimeConstraint.lastValidBlockHeight
) {
return transactionMessage;
}
const out = {
...transactionMessage,
lifetimeConstraint: Object.freeze(blockhashLifetimeConstraint),
};
Object.freeze(out);
return out;
}