-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtime.go
More file actions
204 lines (191 loc) · 8.01 KB
/
Copy pathtime.go
File metadata and controls
204 lines (191 loc) · 8.01 KB
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
package regtest
import (
"context"
"fmt"
"time"
)
// maxMockTime is Bitcoin Core's hard cap on setmocktime arguments. The
// implementation requires the value (multiplied by 1e9 to nanoseconds) to fit
// in an int64; this number is roughly the year 2262.
const maxMockTime int64 = 9_223_372_036
// maxBlockTime is the cap for any timestamp that ends up in a Bitcoin block
// header. The header's nTime field is uint32, so values above this overflow
// when bitcoind constructs a block — generatetoaddress then rejects the
// freshly-built block with the cryptic "time-too-old" error. MineWithTimestamp
// and WarpTime validate against this limit so callers get an immediate,
// actionable error instead. Equivalent to year 2106.
const maxBlockTime int64 = 4_294_967_295
// mtpWindow is the number of blocks Bitcoin Core averages to compute Median
// Time Past (BIP113). Mining mtpWindow + 1 blocks at the same timestamp is
// the simplest way to drag MTP forward to that timestamp — see WarpTime.
const mtpWindow int64 = 11
// SetMockTime sets the node's mocked wall-clock time, used by setmocktime in
// regtest mode. Convenience wrapper around SetMockTimeContext using
// context.Background().
//
// Mocktime affects timestamp generation for subsequent blocks (the default
// coinbase block uses the mocked time when set), BIP9/MTP-gated activations,
// and any other consensus rule keyed on the node's clock. Mocktime persists
// until the process exits or is changed by another setmocktime call.
//
// Parameters:
// - unix: target time as a Unix timestamp in seconds. Must be > 0 and
// ≤ 9_223_372_036 (Bitcoin Core's int64-nanos cap, ~year 2262).
//
// Returns:
// - error: validation error for out-of-range unix; errNotConnected before
// Start; otherwise the wrapped setmocktime RPC error.
//
// Example:
//
// t := time.Now().Add(24 * time.Hour).Unix()
// if err := rt.SetMockTime(t); err != nil { return err }
func (r *Regtest) SetMockTime(unix int64) error {
return r.SetMockTimeContext(context.Background(), unix)
}
// SetMockTimeContext is the context-aware variant of SetMockTime.
//
// Parameters:
// - ctx: cancellation / timeout. A pre-cancelled context returns ctx.Err().
// - unix: target time as a Unix timestamp in seconds. Must be > 0 and
// ≤ maxMockTime.
//
// Returns:
// - error: validation error for out-of-range unix; errNotConnected before
// Start; ctx.Err() on cancellation; wrapped setmocktime RPC error
// otherwise.
func (r *Regtest) SetMockTimeContext(ctx context.Context, unix int64) error {
if unix <= 0 {
return fmt.Errorf("SetMockTime: unix must be > 0, got %d", unix)
}
if unix > maxMockTime {
return fmt.Errorf("SetMockTime: unix must be ≤ %d (Core's int64-nanos cap), got %d", maxMockTime, unix)
}
if _, err := r.rawRPC(ctx, "setmocktime", unix); err != nil {
return fmt.Errorf("SetMockTime: %w", err)
}
return nil
}
// MineWithTimestamp mines blocks all stamped at the supplied unix time. It
// sets the node's mocktime, then calls Warp; coinbase blocks generated by
// generatetoaddress pick up the mocked time. Convenience wrapper around
// MineWithTimestampContext using context.Background().
//
// Mocktime persists after this call returns — subsequent Warp / mining
// operations will continue to use it until the next SetMockTime call.
//
// Parameters:
// - blocks: number of blocks to mine, > 0.
// - unix: target Unix timestamp for each block, > 0 and ≤ maxMockTime.
// - miner: Bitcoin address that receives coinbase rewards.
//
// Returns:
// - error: validation error for blocks ≤ 0, empty miner, or out-of-range
// unix; errNotConnected before Start; wrapped RPC error otherwise.
//
// Example:
//
// t := time.Now().Add(time.Hour).Unix()
// if err := rt.MineWithTimestamp(1, t, addr); err != nil { return err }
// // the new tip's `time` field is now t (within 1s)
func (r *Regtest) MineWithTimestamp(blocks, unix int64, miner string) error {
return r.MineWithTimestampContext(context.Background(), blocks, unix, miner)
}
// MineWithTimestampContext is the context-aware variant of MineWithTimestamp.
//
// Parameters:
// - ctx: cancellation / timeout.
// - blocks: number of blocks to mine, > 0.
// - unix: target Unix timestamp for each block, > 0 and ≤ maxMockTime.
// - miner: Bitcoin address that receives coinbase rewards.
//
// Returns:
// - error: validation error; errNotConnected before Start; ctx.Err() on
// cancellation; wrapped setmocktime / generatetoaddress error otherwise.
func (r *Regtest) MineWithTimestampContext(ctx context.Context, blocks, unix int64, miner string) error {
if blocks <= 0 {
return fmt.Errorf("MineWithTimestamp: blocks must be > 0, got %d", blocks)
}
if miner == "" {
return fmt.Errorf("MineWithTimestamp: miner must be provided")
}
// Stricter than SetMockTime's range: block.nTime is uint32, so anything
// above maxBlockTime overflows when bitcoind constructs the block and
// generatetoaddress fails with "time-too-old". Catch it up front.
if unix > maxBlockTime {
return fmt.Errorf("MineWithTimestamp: unix must be ≤ %d (uint32 block timestamp cap, ~year 2106), got %d", maxBlockTime, unix)
}
if err := r.SetMockTimeContext(ctx, unix); err != nil {
return err
}
return r.WarpContext(ctx, blocks, miner)
}
// WarpTime advances the chain's Median Time Past by the supplied duration.
// It reads the current tip's mediantime, computes target = mediantime +
// duration, sets mocktime to target, and mines mtpWindow + 1 blocks all
// stamped at target so the new tip's MTP equals target. Returns the new
// mediantime as observed via getblockchaininfo. Convenience wrapper around
// WarpTimeContext using context.Background().
//
// Use this for tests that gate on MTP — BIP9 timeout-without-lockin,
// CSV/relative-locktime, BIP113 nLockTime — without mining a full retarget
// window of blocks.
//
// Parameters:
// - duration: amount to advance MTP by, > 0.
// - miner: Bitcoin address that receives coinbase rewards.
//
// Returns:
// - newMTP: the chain's mediantime after the warp, as Unix seconds. Will
// be ≥ original mediantime + duration (within 1s).
// - error: validation error; errNotConnected before Start; wrapped RPC
// error otherwise.
//
// Example:
//
// mtp, err := rt.WarpTime(48*time.Hour, addr)
// if err != nil { return err }
// t.Logf("MTP advanced to %s", time.Unix(mtp, 0))
func (r *Regtest) WarpTime(duration time.Duration, miner string) (int64, error) {
return r.WarpTimeContext(context.Background(), duration, miner)
}
// WarpTimeContext is the context-aware variant of WarpTime.
//
// Parameters:
// - ctx: cancellation / timeout.
// - duration: amount to advance MTP by, > 0.
// - miner: Bitcoin address that receives coinbase rewards.
//
// Returns:
// - newMTP: chain mediantime after the warp.
// - error: validation error (including target > uint32 cap of year 2106);
// errNotConnected before Start; ctx.Err() on cancellation; wrapped RPC
// error otherwise.
func (r *Regtest) WarpTimeContext(ctx context.Context, duration time.Duration, miner string) (int64, error) {
if duration <= 0 {
return 0, fmt.Errorf("WarpTime: duration must be > 0, got %s", duration)
}
if miner == "" {
return 0, fmt.Errorf("WarpTime: miner must be provided")
}
info, err := r.GetBlockChainInfoContext(ctx)
if err != nil {
return 0, fmt.Errorf("WarpTime: read tip: %w", err)
}
target := info.MedianTime + int64(duration.Seconds())
if target > maxBlockTime {
return 0, fmt.Errorf("WarpTime: target %d exceeds uint32 block-timestamp cap %d (~year 2106); pick a smaller duration",
target, maxBlockTime)
}
// Mine mtpWindow + 1 blocks all stamped at target. After mtpWindow + 1
// fresh blocks, the most recent mtpWindow are all at target so the new
// tip's MTP (median of those mtpWindow) equals target.
if err := r.MineWithTimestampContext(ctx, mtpWindow+1, target, miner); err != nil {
return 0, fmt.Errorf("WarpTime: %w", err)
}
postInfo, err := r.GetBlockChainInfoContext(ctx)
if err != nil {
return 0, fmt.Errorf("WarpTime: re-read tip: %w", err)
}
return postInfo.MedianTime, nil
}