• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

libbitcoin / libbitcoin-system / 15084267089

17 May 2025 10:23AM UTC coverage: 81.33% (-0.5%) from 81.805%
15084267089

push

github

web-flow
Merge pull request #1676 from evoskuil/master

Use shared_ptr to make annex a safe copyable object.

33 of 136 new or added lines in 9 files covered. (24.26%)

5 existing lines in 2 files now uncovered.

10385 of 12769 relevant lines covered (81.33%)

3748385.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.0
/src/chain/operation.cpp
1

2
/**
3
 * Copyright (c) 2011-2025 libbitcoin developers (see AUTHORS)
4
 *
5
 * This file is part of libbitcoin.
6
 *
7
 * This program is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU Affero General Public License as published by
9
 * the Free Software Foundation, either version 3 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU Affero General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Affero General Public License
18
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
 */
20
#include <bitcoin/system/chain/operation.hpp>
21

22
#include <algorithm>
23
#include <memory>
24
#include <bitcoin/system/chain/enums/numbers.hpp>
25
#include <bitcoin/system/chain/enums/opcode.hpp>
26
#include <bitcoin/system/data/data.hpp>
27
#include <bitcoin/system/define.hpp>
28
#include <bitcoin/system/machine/machine.hpp>
29
#include <bitcoin/system/math/math.hpp>
30
#include <bitcoin/system/serial/serial.hpp>
31
#include <bitcoin/system/unicode/unicode.hpp>
32

33
namespace libbitcoin {
34
namespace system {
35
namespace chain {
36

37
BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
38

39
// Push data revalidation (op.is_underclaimed()):
40
// Gotta set something when invalid minimal result, check is_valid().
41
// Incorrectly-sized push data is validated upon op parse, setting an invalid
42
// opcode, the underflow bit, and the op data. This invalid opcode will result
43
// in script execution failure if the script is evaluated, but is otherwise
44
// valid (such as for a coinbase input script). Underflow can only occur at the
45
// end of a script. All bytes are valid script, but as scripts have finite
46
// length, the last operation may be an undersized push data. The retention of
47
// the undersized data within an invalid op allows for the script to be
48
// serialized and for the proper size and hash of the transaction computed.
49
// The invalid opcode with underflow data is serialized as data bytes with the
50
// original opcode stored in the data member, but seen as op_verif. By design
51
// it is not possible to populate an op.data size that does not correspond to
52
// the op.code. Size mismatch is revalidated here as final insurance against
53
// derived classes that may alter this behavior. This ensures that an opcode
54
// that does not push correctly-sized data will fail.
55
static constexpr auto any_invalid = opcode::op_verif;
56
static_assert(operation::is_invalid(any_invalid));
57

58
// Null data helpers.
59
// ----------------------------------------------------------------------------
60

61
// static/private
62
const data_chunk& operation::no_data() NOEXCEPT
153✔
63
{
64
    static const data_chunk empty{};
153✔
65
    return empty;
153✔
66
}
67

68
// static/private
69
const chunk_cptr& operation::no_data_cptr() NOEXCEPT
×
70
{
NEW
71
    static const auto empty = to_shared<const data_chunk>();
×
72
    return empty;
×
73
}
74

75
// static/private
76
// Push data is not possible with an invalid code, combination is invalid.
77
const chunk_cptr& operation::any_data_cptr() NOEXCEPT
10✔
78
{
79
    static const auto any = to_shared(data_chunk{ 0x42 });
12✔
80
    return any;
10✔
81
}
82

83
bool operation::data_empty() const NOEXCEPT
84✔
84
{
85
    return !data_ || data_->empty();
84✔
86
}
87

88
size_t operation::data_size() const NOEXCEPT
1,529,546✔
89
{
90
    return data_ ? data_->size() : zero;
1,529,546✔
91
}
92

93
const data_chunk& operation::get_data() const NOEXCEPT
1,492,352✔
94
{
95
    return data_ ? *data_ : no_data();
1,492,352✔
96
}
97

98
const chunk_cptr& operation::get_data_cptr() const NOEXCEPT
4,498✔
99
{
100
    return data_ ? data_ : no_data_cptr();
4,498✔
101
}
102

103
// Constructors.
104
// ----------------------------------------------------------------------------
105

106
operation::operation() NOEXCEPT
10✔
107
  : operation(any_invalid, any_data_cptr(), false)
10✔
108
{
109
}
10✔
110

111
// If code is push data the data member will be inconsistent (empty).
112
operation::operation(opcode code) NOEXCEPT
450✔
113
  : operation(code, nullptr, false)
450✔
114
{
115
}
450✔
116

117
operation::operation(data_chunk&& push_data, bool minimal) NOEXCEPT
39✔
118
  : operation(from_push_data(to_shared(std::move(push_data)), minimal))
39✔
119
{
120
}
39✔
121

122
operation::operation(const data_chunk& push_data, bool minimal) NOEXCEPT
5✔
123
  : operation(from_push_data(to_shared(push_data), minimal))
5✔
124
{
125
}
5✔
126

127
operation::operation(const chunk_cptr& push_data, bool minimal) NOEXCEPT
5✔
128
  : operation(from_push_data(push_data, minimal))
5✔
129
{
130
}
5✔
131

132
operation::operation(stream::in::fast&& stream) NOEXCEPT
35✔
133
  : operation(read::bytes::fast(stream))
35✔
134
{
135
}
35✔
136

137
operation::operation(stream::in::fast& stream) NOEXCEPT
1✔
138
  : operation(read::bytes::fast(stream))
1✔
139
{
140
}
1✔
141

142
operation::operation(std::istream&& stream) NOEXCEPT
×
143
  : operation(read::bytes::istream(stream))
×
144
{
145
}
×
146

147
operation::operation(std::istream& stream) NOEXCEPT
4✔
148
  : operation(read::bytes::istream(stream))
4✔
149
{
150
}
4✔
151

152
operation::operation(reader&& source) NOEXCEPT
40✔
153
  : operation(source)
40✔
154
{
155
}
×
156

157
operation::operation(reader& source) NOEXCEPT
82,738✔
158
{
159
    assign_data(source);
82,738✔
160
}
82,738✔
161

162
operation::operation(const std::string& mnemonic) NOEXCEPT
10,738✔
163
  : operation(from_string(mnemonic))
10,738✔
164
{
165
}
10,738✔
166

167
// protected
168
operation::operation(opcode code, const chunk_cptr& push_data,
11,241✔
169
    bool underflow) NOEXCEPT
460✔
170
  : code_(code), data_(push_data), underflow_(underflow)
11,241✔
171
{
172
}
×
173

174
// Operators.
175
// ----------------------------------------------------------------------------
176

177
bool operation::operator==(const operation& other) const NOEXCEPT
236✔
178
{
179
    return (code_ == other.code_)
236✔
180
        && (data_ == other.data_ || get_data() == other.get_data())
234✔
181
        && (underflow_ == other.underflow_);
470✔
182
}
183

184
bool operation::operator!=(const operation& other) const NOEXCEPT
2✔
185
{
186
    return !(*this == other);
2✔
187
}
188

189
// Deserialization.
190
// ----------------------------------------------------------------------------
191

192
// private
193
void operation::assign_data(reader& source) NOEXCEPT
82,738✔
194
{
195
    auto& allocator = source.get_allocator();
82,738✔
196

197
    // Guard against resetting a previously-invalid stream.
198
    if (!source)
82,738✔
199
    {
200
        INPLACE(&data_, data_chunk, allocator, nullptr);
×
201
        return;
82,727✔
202
    }
203

204
    // If stream is not empty then a non-data opcode will always deserialize.
205
    // A push-data opcode may indicate more bytes than are available. In this
206
    // case the the script is invalid, but it may not be evaluated, such as
207
    // with a coinbase input. So if an operation fails to deserialize it is
208
    // re-read and retained as an "underflow" operation. An underflow op
209
    // serializes as data only, and fails evaluation. Only the last operation
210
    // in a script could become an underflow, which may possibly contain the
211
    // entire script. This retains the read position in case of underflow.
212
    const auto start = source.get_read_position();
82,738✔
213

214
    // Size of a push-data opcode is not retained, as this is inherent in data.
215
    code_ = static_cast<opcode>(source.read_byte());
82,738✔
216
    const auto size = read_data_size(code_, source);
82,738✔
217

218
    // read_bytes only guarded from excessive allocation by stream limit.
219
    if (size > max_block_size)
82,738✔
220
        source.invalidate();
1✔
221

222
    // An invalid source.read_bytes_raw returns nullptr.
223
    const auto ptr = source.read_bytes_raw(size);
82,738✔
224
    underflow_ = !source;
82,738✔
225
    if (!underflow_)
82,738✔
226
    {
227
        INPLACE(&data_, data_chunk, allocator, ptr);
82,727✔
228
        return;
82,727✔
229
    }
230

231
    // This requires that provided stream terminates at the end of the script.
232
    // When passing ops as part of a stream longer than the script, such as for
233
    // a transaction, caller should apply source.set_limit(prefix_size), and
234
    // clear the stream limit upon return. Stream invalidation and set_position
235
    // do not alter a stream limit, it just behaves as a smaller stream buffer.
236
    // Without a limit, source.read_bytes() below consumes the remaining stream.
237
    code_ = any_invalid;
11✔
238
    source.set_position(start);
11✔
239
    INPLACE(&data_, data_chunk, allocator, source.read_bytes_raw());
11✔
240
}
241

242
// static/private
243
operation operation::from_push_data(const chunk_cptr& data,
49✔
244
    bool minimal) NOEXCEPT
245
{
246
    const auto code = opcode_from_data(*data, minimal);
49✔
247

248
    // Minimal interpretation affects only single byte push data.
249
    // Revert data if (minimal) opcode_from_data produced a numeric encoding.
250
    const auto push = is_payload(code) ? data : nullptr;
49✔
251

252
    return { code, push, false };
49✔
253
}
254

255
// Serialization.
256
// ----------------------------------------------------------------------------
257

258
data_chunk operation::to_data() const NOEXCEPT
24✔
259
{
260
    data_chunk data(serialized_size());
24✔
261
    stream::out::fast ostream(data);
24✔
262
    write::bytes::fast out(ostream);
24✔
263
    to_data(out);
24✔
264
    return data;
48✔
265
}
24✔
266

267
void operation::to_data(std::ostream& stream) const NOEXCEPT
1✔
268
{
269
    write::bytes::ostream out(stream);
1✔
270
    to_data(out);
1✔
271
}
1✔
272

273
void operation::to_data(writer& sink) const NOEXCEPT
1,491,443✔
274
{
275
    // Underflow is op-undersized data, it is serialized with no opcode.
276
    // An underflow could only be a final token in a script deserialization.
277
    if (is_underflow())
1,491,443✔
278
    {
279
        sink.write_bytes(get_data());
14✔
280
    }
281
    else
282
    {
283
        const auto size = data_size();
1,491,429✔
284
        sink.write_byte(static_cast<uint8_t>(code_));
1,491,429✔
285

286
        switch (code_)
1,491,429✔
287
        {
288
            case opcode::push_one_size:
2✔
289
                sink.write_byte(narrow_cast<uint8_t>(size));
2✔
290
                break;
2✔
291
            case opcode::push_two_size:
3✔
292
                sink.write_2_bytes_little_endian(narrow_cast<uint16_t>(size));
3✔
293
                break;
3✔
294
            case opcode::push_four_size:
2✔
295
                sink.write_4_bytes_little_endian(
2✔
296
                    possible_narrow_cast<uint32_t>(size));
297
                break;
2✔
298
            default:
299
            break;
300
        }
301

302
        sink.write_bytes(get_data());
1,491,429✔
303
    }
304
}
1,491,443✔
305

306
// Text.
307
// ----------------------------------------------------------------------------
308

309
inline bool is_push_token(const std::string& token) NOEXCEPT
10,738✔
310
{
311
    return token.size() > one && token.front() == '[' && token.back() == ']';
10,738✔
312
}
313

314
inline bool is_text_token(const std::string& token) NOEXCEPT
10,579✔
315
{
316
    return token.size() > one && token.front() == '\'' && token.back() == '\'';
10,579✔
317
}
318

319
inline bool is_underflow_token(const std::string& token) NOEXCEPT
9,334✔
320
{
321
    return token.size() > one && token.front() == '<' && token.back() == '>';
9,334✔
322
}
323

324
inline std::string remove_token_delimiters(const std::string& token) NOEXCEPT
1,419✔
325
{
326
    BC_ASSERT(token.size() > one);
1,419✔
327
    return std::string(std::next(token.begin()), std::prev(token.end()));
1,419✔
328
}
329

330
inline string_list split_push_token(const std::string& token) NOEXCEPT
159✔
331
{
332
    return split(remove_token_delimiters(token), ".", false, false);
318✔
333
}
334

335
static bool opcode_from_data_prefix(opcode& out_code,
23✔
336
    const std::string& prefix, const data_chunk& push_data) NOEXCEPT
337
{
338
    constexpr auto op_75 = static_cast<uint8_t>(opcode::push_size_75);
23✔
339
    const auto size = push_data.size();
23✔
340
    out_code = operation::opcode_from_size(size);
23✔
341

342
    if (prefix == "0")
23✔
343
    {
344
        return size <= op_75;
2✔
345
    }
346
    else if (prefix == "1")
21✔
347
    {
348
        out_code = opcode::push_one_size;
11✔
349
        return size <= max_uint8;
11✔
350
    }
351
    else if (prefix == "2")
10✔
352
    {
353
        out_code = opcode::push_two_size;
5✔
354
        return size <= max_uint16;
5✔
355
    }
356
    else if (prefix == "4")
5✔
357
    {
358
        out_code = opcode::push_four_size;
3✔
359
        return size <= max_uint32;
3✔
360
    }
361

362
    return false;
363
}
364

365
static bool data_from_decimal(data_chunk& out_data,
296✔
366
    const std::string& token) NOEXCEPT
367
{
368
    // Deserialization to a number can convert random text to zero.
369
    if (!is_ascii_numeric(token))
296✔
370
        return false;
371

372
    int64_t value;
296✔
373
    if (!deserialize(value, token))
296✔
374
        return false;
375

376
    out_data = machine::number::chunk::from_integer(value);
296✔
377
    return true;
296✔
378
}
379

380
// private/static
381
operation operation::from_string(const std::string& mnemonic) NOEXCEPT
10,738✔
382
{
383
    data_chunk chunk;
10,738✔
384
    auto valid = false;
10,738✔
385
    auto underflow = false;
10,738✔
386

387
    // Always defined below, but this fixes warning.
388
    opcode code{ any_invalid };
10,738✔
389

390
    if (is_push_token(mnemonic))
10,738✔
391
    {
392
        // Data encoding uses single token with one or two parts.
393
        const auto parts = split_push_token(mnemonic);
159✔
394

395
        if (parts.size() == one)
159✔
396
        {
397
            // Extract operation using nominal data size decoding.
398
            if ((valid = decode_base16(chunk, parts.front())))
133✔
399
                code = nominal_opcode_from_data(chunk);
133✔
400
        }
401
        else if (parts.size() == two)
26✔
402
        {
403
            // Extract operation using explicit data size decoding.
404

405
            // More efficient [] dereference is guarded above.
406
            BC_PUSH_WARNING(NO_ARRAY_INDEXING)
407
            valid = decode_base16(chunk, parts[1]) &&
49✔
408
                opcode_from_data_prefix(code, parts[0], chunk);
23✔
409
            BC_POP_WARNING()
410
        }
411
    }
159✔
412
    else if (is_text_token(mnemonic))
10,579✔
413
    {
414
        // Extract operation using nominal data size decoding.
415
        chunk = to_chunk(remove_token_delimiters(mnemonic));
1,245✔
416
        code = nominal_opcode_from_data(chunk);
1,245✔
417
        valid = true;
1,245✔
418
    }
419
    else if (is_underflow_token(mnemonic))
9,334✔
420
    {
421
        // code is ignored for underflow ops.
422
        underflow = true;
2✔
423
        code = any_invalid;
2✔
424
        valid = decode_base16(chunk, remove_token_delimiters(mnemonic));
4✔
425
    }
426
    else if (opcode_from_mnemonic(code, mnemonic))
9,332✔
427
    {
428
        // Any push code may have empty data, so this is presumed here.
429
        // No data is obtained here from a push opcode (use push/text tokens).
430
        valid = true;
431
    }
432
    else if (data_from_decimal(chunk, mnemonic))
296✔
433
    {
434
        // opcode_from_mnemonic captures [-1, 0, 1..16] integers, others here.
435
        code = nominal_opcode_from_data(chunk);
296✔
436
        valid = true;
296✔
437
    }
438

439
    if (!valid)
10,738✔
440
        return {};
6✔
441

442
    return { code, to_shared(std::move(chunk)), underflow };
21,464✔
443
}
444

445
static std::string opcode_to_prefix(opcode code,
16✔
446
    const data_chunk& data) NOEXCEPT
447
{
448
    // If opcode is minimal for a size-based encoding, do not set a prefix.
449
    if (code == operation::opcode_from_size(data.size()))
16✔
450
        return "";
13✔
451

452
    switch (code)
3✔
453
    {
454
        case opcode::push_one_size:
1✔
455
            return "1.";
1✔
456
        case opcode::push_two_size:
1✔
457
            return "2.";
1✔
458
        case opcode::push_four_size:
1✔
459
            return "4.";
1✔
460
        default:
×
461
            return "0.";
×
462
    }
463
}
464

465
std::string operation::to_string(uint32_t active_flags) const NOEXCEPT
77✔
466
{
467
    if (!is_valid())
77✔
468
        return "(?)";
×
469

470
    if (underflow_)
77✔
471
        return "<" + encode_base16(get_data()) + ">";
4✔
472

473
    if (data_empty())
75✔
474
        return opcode_to_mnemonic(code_, active_flags);
59✔
475

476
    // Data encoding uses single token with explicit size prefix as required.
477
    return "[" + opcode_to_prefix(code_, get_data()) +
32✔
478
        encode_base16(get_data()) + "]";
48✔
479
}
480

481
// Properties.
482
// ----------------------------------------------------------------------------
483

484
bool operation::is_valid() const NOEXCEPT
10,800✔
485
{
486
    // Push data not possible with any is_invalid, combination is invalid.
487
    // This is necessary because there can be no invalid sentinel value.
488
    return !(code_ == any_invalid && !underflow_ && !data_empty());
10,800✔
489
}
490

491
opcode operation::code() const NOEXCEPT
1,227,401✔
492
{
493
    return code_;
1,227,401✔
494
}
495

496
const data_chunk& operation::data() const NOEXCEPT
330✔
497
{
498
    return get_data();
330✔
499
}
500

501
const chunk_cptr& operation::data_ptr() const NOEXCEPT
4,498✔
502
{
503
    return get_data_cptr();
4,498✔
504
}
505

506
size_t operation::serialized_size() const NOEXCEPT
11,230✔
507
{
508
    static constexpr auto op_size = sizeof(uint8_t);
11,230✔
509
    const auto size = data_size();
11,230✔
510

511
    if (underflow_)
11,230✔
512
        return size;
513

514
    switch (code_)
11,227✔
515
    {
516
        case opcode::push_one_size:
14✔
517
            return op_size + sizeof(uint8_t) + size;
14✔
518
        case opcode::push_two_size:
67✔
519
            return op_size + sizeof(uint16_t) + size;
67✔
520
        case opcode::push_four_size:
5✔
521
            return op_size + sizeof(uint32_t) + size;
5✔
522
        default:
11,141✔
523
            return op_size + size;
11,141✔
524
    }
525
}
526

527
// Utilities.
528
// ----------------------------------------------------------------------------
529

530
// static/private
531
// Advances stream, returns true unless exhausted.
532
// Does not advance to end position in the case of underflow operation.
533
bool operation::count_op(reader& source) NOEXCEPT
83,394✔
534
{
535
    if (source.is_exhausted())
83,394✔
536
        return false;
537

538
    const auto code = static_cast<opcode>(source.read_byte());
82,696✔
539
    source.skip_bytes(read_data_size(code, source));
82,696✔
540
    return true;
82,696✔
541
}
542

543
// static/private
544
uint32_t operation::read_data_size(opcode code, reader& source) NOEXCEPT
165,434✔
545
{
546
    constexpr auto op_75 = static_cast<uint8_t>(opcode::push_size_75);
165,434✔
547

548
    switch (code)
165,434✔
549
    {
550
        case opcode::push_one_size:
11✔
551
            return source.read_byte();
11✔
552
        case opcode::push_two_size:
9✔
553
            return source.read_2_bytes_little_endian();
9✔
554
        case opcode::push_four_size:
5✔
555
            return source.read_4_bytes_little_endian();
5✔
556
        default:
165,409✔
557
            const auto byte = static_cast<uint8_t>(code);
165,409✔
558
            return byte <= op_75 ? byte : 0;
165,409✔
559
    }
560
}
561

562
BC_POP_WARNING()
563

564
// JSON value convertors.
565
// ----------------------------------------------------------------------------
566

567
namespace json = boost::json;
568

569
// boost/json will soon have NOEXCEPT: github.com/boostorg/json/pull/636
570
BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
571

572
operation tag_invoke(json::value_to_tag<operation>,
1✔
573
    const json::value& value) NOEXCEPT
574
{
575
    return operation{ std::string(value.get_string().c_str()) };
1✔
576
}
577

578
void tag_invoke(json::value_from_tag, json::value& value,
2✔
579
    const operation& operation) NOEXCEPT
580
{
581
    value = operation.to_string(flags::all_rules);
2✔
582
}
2✔
583

584
BC_POP_WARNING()
585

586
operation::cptr tag_invoke(json::value_to_tag<operation::cptr>,
×
587
    const json::value& value) NOEXCEPT
588
{
589
    return to_shared(tag_invoke(json::value_to_tag<operation>{}, value));
×
590
}
591

592
// Shared pointer overload is required for navigation.
593
BC_PUSH_WARNING(SMART_PTR_NOT_NEEDED)
594
BC_PUSH_WARNING(NO_VALUE_OR_CONST_REF_SHARED_PTR)
595

596
void tag_invoke(json::value_from_tag tag, json::value& value,
×
597
    const operation::cptr& operation) NOEXCEPT
598
{
599
    tag_invoke(tag, value, *operation);
×
600
}
×
601

602
BC_POP_WARNING()
603
BC_POP_WARNING()
604

605
} // namespace chain
606
} // namespace system
607
} // namespace libbitcoin
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc