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

libbitcoin / libbitcoin-system / 9310800761

31 May 2024 12:08AM UTC coverage: 82.732% (-0.01%) from 82.746%
9310800761

push

github

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

Performance optimizations, style, comments.

233 of 289 new or added lines in 11 files covered. (80.62%)

8 existing lines in 4 files now uncovered.

9812 of 11860 relevant lines covered (82.73%)

4808594.09 hits per line

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

89.49
/src/chain/operation.cpp
1

2
/**
3
 * Copyright (c) 2011-2023 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
// Gotta set something when invalid minimal result, test is_valid.
38
static constexpr auto any_invalid = opcode::op_xor;
39

40
// static
41
chunk_cptr operation::no_data_ptr() NOEXCEPT
124✔
42
{
43
    static const auto empty = to_shared<data_chunk>();
125✔
44
    return empty;
124✔
45
}
46

47
// static
48
chunk_cptr operation::any_data_ptr() NOEXCEPT
10✔
49
{
50
    // Push data is not possible with an invalid code, combination is invalid.
51
    static const auto any = to_shared<data_chunk>({ 0x42 });
12✔
52
    return any;
10✔
53
}
54

55
// Constructors.
56
// ----------------------------------------------------------------------------
57

58
operation::operation() NOEXCEPT
10✔
59
  : operation(any_invalid, any_data_ptr(), false)
10✔
60
{
61
}
10✔
62

63
// If code is push data the data member will be inconsistent (empty).
64
operation::operation(opcode code) NOEXCEPT
120✔
65
  : operation(code, no_data_ptr(), false)
120✔
66
{
67
}
120✔
68

69
operation::operation(data_chunk&& push_data, bool minimal) NOEXCEPT
40✔
70
  : operation(from_push_data(to_shared(std::move(push_data)), minimal))
40✔
71
{
72
}
40✔
73

74
operation::operation(const data_chunk& push_data, bool minimal) NOEXCEPT
5✔
75
  : operation(from_push_data(to_shared(push_data), minimal))
5✔
76
{
77
}
5✔
78

79
operation::operation(const chunk_cptr& push_data, bool minimal) NOEXCEPT
×
80
  : operation(from_push_data(push_data, minimal))
×
81
{
82
}
×
83

84
operation::operation(const data_slice& op_data) NOEXCEPT
35✔
85
    BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
86
  : operation(stream::in::copy(op_data))
35✔
87
    BC_POP_WARNING()
88
{
89
}
35✔
90

91
////operation::operation(stream::in::fast&& stream) NOEXCEPT
92
////  : operation(read::bytes::fast(stream))
93
////{
94
////}
95

96
operation::operation(stream::in::fast& stream) NOEXCEPT
1✔
97
  : operation(read::bytes::fast(stream))
1✔
98
{
99
}
1✔
100

101
operation::operation(std::istream&& stream) NOEXCEPT
35✔
102
  : operation(read::bytes::istream(stream))
35✔
103
{
104
}
35✔
105

106
operation::operation(std::istream& stream) NOEXCEPT
4✔
107
  : operation(read::bytes::istream(stream))
4✔
108
{
109
}
4✔
110

111
operation::operation(reader&& source) NOEXCEPT
40✔
112
  : operation(from_data(source))
40✔
113
{
114
}
40✔
115

116
operation::operation(reader& source) NOEXCEPT
82,516✔
117
  : operation(from_data(source))
82,516✔
118
{
119
}
82,516✔
120

121
operation::operation(const std::string& mnemonic) NOEXCEPT
10,729✔
122
  : operation(from_string(mnemonic))
10,729✔
123
{
124
}
10,729✔
125

126
// protected
127
operation::operation(opcode code, const chunk_cptr& push_data,
93,454✔
128
    bool underflow) NOEXCEPT
130✔
129
  : code_(code), data_(push_data), underflow_(underflow)
93,454✔
130
{
131
}
×
132

133
// Operators.
134
// ----------------------------------------------------------------------------
135

136
bool operation::operator==(const operation& other) const NOEXCEPT
188✔
137
{
138
    return (code_ == other.code_)
188✔
139
        && (data_ == other.data_ || *data_ == *other.data_)
178✔
140
        && (underflow_ == other.underflow_);
366✔
141
}
142

143
bool operation::operator!=(const operation& other) const NOEXCEPT
2✔
144
{
145
    return !(*this == other);
2✔
146
}
147

148
// Deserialization.
149
// ----------------------------------------------------------------------------
150

151
// static/private
152
operation operation::from_data(reader& source) NOEXCEPT
82,556✔
153
{
154
    // Guard against resetting a previously-invalid stream.
155
    if (!source)
82,556✔
156
        return {};
×
157

158
    // If stream is not empty then a non-data opcode will always deserialize.
159
    // A push-data opcode may indicate more bytes than are available. In this
160
    // case the the script is invalid, but it may not be evaluated, such as
161
    // with a coinbase input. So if an operation fails to deserialize it is
162
    // re-read and retained as an "underflow" operation. An underflow op
163
    // serializes as data only, and fails evaluation. Only the last operation
164
    // in a script could become an underflow, which may possibly contain the
165
    // entire script. This retains the read position in case of underflow.
166
    const auto start = source.get_read_position();
82,556✔
167

168
    // Size of a push-data opcode is not retained, as this is inherent in data.
169
    auto code = static_cast<opcode>(source.read_byte());
82,556✔
170
    const auto size = read_data_size(code, source);
82,556✔
171

172
    // read_bytes only guarded from excessive allocation by stream limit.
173
    if (size > max_block_size)
82,556✔
174
        source.invalidate();
1✔
175

176
    auto push = to_shared(source ? source.read_bytes(size) : data_chunk{});
82,556✔
177
    const auto underflow = !source;
82,556✔
178

179
    // This requires that provided stream terminates at the end of the script.
180
    // When passing ops as part of a stream longer than the script, such as for
181
    // a transaction, caller should apply source.set_limit(prefix_size), and
182
    // clear the stream limit upon return. Stream invalidation and set_position
183
    // do not alter a stream limit, it just behaves as a smaller stream buffer.
184
    // Without a limit, source.read_bytes() below consumes the remaining stream.
185
    if (underflow)
82,556✔
186
    {
187
        code = any_invalid;
11✔
188
        source.set_position(start);
11✔
189
        push = to_shared(source.read_bytes());
22✔
190
    }
191

192
    // All byte vectors are deserializable, stream indicates own failure.
193
    return { code, push, underflow };
82,556✔
194
}
195

196
// static/private
197
operation operation::from_push_data(const chunk_cptr& data,
45✔
198
    bool minimal) NOEXCEPT
199
{
200
    const auto code = opcode_from_data(*data, minimal);
45✔
201

202
    // Minimal interpretation affects only single byte push data.
203
    // Revert data if (minimal) opcode_from_data produced a numeric encoding.
204
    const auto push = is_payload(code) ? data : no_data_ptr();
45✔
205

206
    return { code, push, false };
45✔
207
}
208

209
inline bool is_push_token(const std::string& token) NOEXCEPT
10,729✔
210
{
211
    return token.size() > one && token.front() == '[' && token.back() == ']';
10,729✔
212
}
213

214
inline bool is_text_token(const std::string& token) NOEXCEPT
10,572✔
215
{
216
    return token.size() > one && token.front() == '\'' && token.back() == '\'';
10,572✔
217
}
218

219
inline bool is_underflow_token(const std::string& token) NOEXCEPT
9,327✔
220
{
221
    return token.size() > one && token.front() == '<' && token.back() == '>';
9,327✔
222
}
223

224
inline std::string remove_token_delimiters(const std::string& token) NOEXCEPT
1,417✔
225
{
226
    BC_ASSERT(token.size() > one);
1,417✔
227
    return std::string(std::next(token.begin()), std::prev(token.end()));
1,417✔
228
}
229

230
inline string_list split_push_token(const std::string& token) NOEXCEPT
157✔
231
{
232
    return split(remove_token_delimiters(token), ".", false, false);
314✔
233
}
234

235
static bool opcode_from_data_prefix(opcode& out_code,
22✔
236
    const std::string& prefix, const data_chunk& push_data) NOEXCEPT
237
{
238
    constexpr auto op_75 = static_cast<uint8_t>(opcode::push_size_75);
22✔
239
    const auto size = push_data.size();
22✔
240
    out_code = operation::opcode_from_size(size);
22✔
241

242
    if (prefix == "0")
22✔
243
    {
244
        return size <= op_75;
2✔
245
    }
246
    else if (prefix == "1")
20✔
247
    {
248
        out_code = opcode::push_one_size;
10✔
249
        return size <= max_uint8;
10✔
250
    }
251
    else if (prefix == "2")
10✔
252
    {
253
        out_code = opcode::push_two_size;
5✔
254
        return size <= max_uint16;
5✔
255
    }
256
    else if (prefix == "4")
5✔
257
    {
258
        out_code = opcode::push_four_size;
3✔
259
        return size <= max_uint32;
3✔
260
    }
261

262
    return false;
263
}
264

265
static bool data_from_decimal(data_chunk& out_data,
296✔
266
    const std::string& token) NOEXCEPT
267
{
268
    // Deserialization to a number can convert random text to zero.
269
    if (!is_ascii_numeric(token))
296✔
270
        return false;
271

272
    int64_t value;
296✔
273
    if (!deserialize(value, token))
296✔
274
        return false;
275

276
    out_data = machine::number::chunk::from_integer(value);
296✔
277
    return true;
296✔
278
}
279

280
// private/static
281
operation operation::from_string(const std::string& mnemonic) NOEXCEPT
10,729✔
282
{
283
    data_chunk chunk;
10,729✔
284
    auto valid = false;
10,729✔
285
    auto underflow = false;
10,729✔
286

287
    // Always defined below, but this fixes warning.
288
    opcode code{ opcode::op_xor };
10,729✔
289

290
    if (is_push_token(mnemonic))
10,729✔
291
    {
292
        // Data encoding uses single token with one or two parts.
293
        const auto parts = split_push_token(mnemonic);
157✔
294

295
        if (parts.size() == 1)
157✔
296
        {
297
            // Extract operation using nominal data size decoding.
298
            if ((valid = decode_base16(chunk, parts.front())))
132✔
299
                code = nominal_opcode_from_data(chunk);
132✔
300
        }
301
        else if (parts.size() == 2)
25✔
302
        {
303
            // Extract operation using explicit data size decoding.
304

305
            // More efficient [] dereference is guarded above.
306
            BC_PUSH_WARNING(NO_ARRAY_INDEXING)
307
            valid = decode_base16(chunk, parts[1]) &&
47✔
308
                opcode_from_data_prefix(code, parts[0], chunk);
22✔
309
            BC_POP_WARNING()
310
        }
311
    }
157✔
312
    else if (is_text_token(mnemonic))
10,572✔
313
    {
314
        // Extract operation using nominal data size decoding.
315
        chunk = to_chunk(remove_token_delimiters(mnemonic));
2,490✔
316
        code = nominal_opcode_from_data(chunk);
1,245✔
317
        valid = true;
1,245✔
318
    }
319
    else if (is_underflow_token(mnemonic))
9,327✔
320
    {
321
        // code is ignored for underflow ops.
322
        underflow = true;
2✔
323
        code = any_invalid;
2✔
324
        valid = decode_base16(chunk, remove_token_delimiters(mnemonic));
4✔
325
    }
326
    else if (opcode_from_mnemonic(code, mnemonic))
9,325✔
327
    {
328
        // Any push code may have empty data, so this is presumed here.
329
        // No data is obtained here from a push opcode (use push/text tokens).
330
        valid = true;
331
    }
332
    else if (data_from_decimal(chunk, mnemonic))
296✔
333
    {
334
        // opcode_from_mnemonic captures [-1, 0, 1..16] integers, others here.
335
        code = nominal_opcode_from_data(chunk);
296✔
336
        valid = true;
296✔
337
    }
338

339
    if (!valid)
10,729✔
340
        return {};
6✔
341

342
    return { code, to_shared(std::move(chunk)), underflow };
21,446✔
343
}
344

345
// Serialization.
346
// ----------------------------------------------------------------------------
347

348
data_chunk operation::to_data() const NOEXCEPT
24✔
349
{
350
    data_chunk data(serialized_size());
24✔
351

352
    BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
353
    stream::out::copy ostream(data);
24✔
354
    BC_POP_WARNING()
355

356
    to_data(ostream);
24✔
357
    return data;
48✔
358
}
24✔
359

360
void operation::to_data(std::ostream& stream) const NOEXCEPT
25✔
361
{
362
    write::bytes::ostream out(stream);
25✔
363
    to_data(out);
25✔
364
}
25✔
365

366
void operation::to_data(writer& sink) const NOEXCEPT
1,491,421✔
367
{
368
    // Underflow is op-undersized data, it is serialized with no opcode.
369
    // An underflow could only be a final token in a script deserialization.
370
    if (is_underflow())
1,491,421✔
371
    {
372
        sink.write_bytes(*data_);
14✔
373
    }
374
    else
375
    {
376
        const auto size = data_->size();
1,491,407✔
377
        sink.write_byte(static_cast<uint8_t>(code_));
1,491,407✔
378

379
        switch (code_)
1,491,407✔
380
        {
381
            case opcode::push_one_size:
2✔
382
                sink.write_byte(narrow_cast<uint8_t>(size));
2✔
383
                break;
2✔
384
            case opcode::push_two_size:
3✔
385
                sink.write_2_bytes_little_endian(narrow_cast<uint16_t>(size));
3✔
386
                break;
3✔
387
            case opcode::push_four_size:
2✔
388
                sink.write_4_bytes_little_endian(
2✔
389
                    possible_narrow_cast<uint32_t>(size));
390
                break;
2✔
391
            default:
392
            break;
393
        }
394

395
        sink.write_bytes(*data_);
1,491,407✔
396
    }
397
}
1,491,421✔
398

399
// To String.
400
// ----------------------------------------------------------------------------
401

402
static std::string opcode_to_prefix(opcode code,
16✔
403
    const data_chunk& data) NOEXCEPT
404
{
405
    // If opcode is minimal for a size-based encoding, do not set a prefix.
406
    if (code == operation::opcode_from_size(data.size()))
16✔
407
        return "";
13✔
408

409
    switch (code)
3✔
410
    {
411
        case opcode::push_one_size:
1✔
412
            return "1.";
1✔
413
        case opcode::push_two_size:
1✔
414
            return "2.";
1✔
415
        case opcode::push_four_size:
1✔
416
            return "4.";
1✔
417
        default:
×
418
            return "0.";
×
419
    }
420
}
421

422
std::string operation::to_string(uint32_t active_flags) const NOEXCEPT
77✔
423
{
424
    if (!is_valid())
77✔
425
        return "(?)";
×
426

427
    if (underflow_)
77✔
428
        return "<" + encode_base16(*data_) + ">";
4✔
429

430
    if (data_->empty())
75✔
431
        return opcode_to_mnemonic(code_, active_flags);
59✔
432

433
    // Data encoding uses single token with explicit size prefix as required.
434
    return "[" + opcode_to_prefix(code_, *data_) + encode_base16(*data_) + "]";
32✔
435
}
436

437
// Properties.
438
// ----------------------------------------------------------------------------
439

440
bool operation::is_valid() const NOEXCEPT
10,791✔
441
{
442
    // Push data not possible with any is_invalid, combination is invalid.
443
    // This is necessary because there can be no invalid sentinel value.
444
    return !(code_ == any_invalid && !underflow_ && !data_->empty());
10,791✔
445
}
446

447
opcode operation::code() const NOEXCEPT
1,227,365✔
448
{
449
    return code_;
1,227,365✔
450
}
451

452
const data_chunk& operation::data() const NOEXCEPT
378✔
453
{
454
    return *data_;
378✔
455
}
456

457
const chunk_cptr& operation::data_ptr() const NOEXCEPT
4,470✔
458
{
459
    return data_;
4,470✔
460
}
461

462
size_t operation::serialized_size() const NOEXCEPT
10,887✔
463
{
464
    static constexpr auto op_size = sizeof(uint8_t);
10,887✔
465
    const auto size = data_->size();
10,887✔
466

467
    if (underflow_)
10,887✔
468
        return size;
469

470
    switch (code_)
10,884✔
471
    {
472
        case opcode::push_one_size:
13✔
473
            return op_size + sizeof(uint8_t) + size;
13✔
474
        case opcode::push_two_size:
67✔
475
            return op_size + sizeof(uint16_t) + size;
67✔
476
        case opcode::push_four_size:
5✔
477
            return op_size + sizeof(uint32_t) + size;
5✔
478
        default:
10,799✔
479
            return op_size + size;
10,799✔
480
    }
481
}
482

483
// Utilities.
484
// ----------------------------------------------------------------------------
485

486
// static/private
487
// Advances stream, returns true unless exhausted.
488
// Does not advance to end position in the case of underflow operation.
489
bool operation::count_op(reader& source) NOEXCEPT
83,132✔
490
{
491
    if (source.is_exhausted())
83,132✔
492
        return false;
493

494
    const auto code = static_cast<opcode>(source.read_byte());
82,514✔
495
    source.skip_bytes(read_data_size(code, source));
82,514✔
496
    return true;
82,514✔
497
}
498

499
// static/private
500
uint32_t operation::read_data_size(opcode code, reader& source) NOEXCEPT
165,070✔
501
{
502
    constexpr auto op_75 = static_cast<uint8_t>(opcode::push_size_75);
165,070✔
503

504
    switch (code)
165,070✔
505
    {
506
        case opcode::push_one_size:
11✔
507
            return source.read_byte();
11✔
508
        case opcode::push_two_size:
9✔
509
            return source.read_2_bytes_little_endian();
9✔
510
        case opcode::push_four_size:
5✔
511
            return source.read_4_bytes_little_endian();
5✔
512
        default:
165,045✔
513
            const auto byte = static_cast<uint8_t>(code);
165,045✔
514
            return byte <= op_75 ? byte : 0;
165,045✔
515
    }
516
}
517

518
// Categories of operations.
519
// ----------------------------------------------------------------------------
520

521
bool operation::is_invalid() const NOEXCEPT
115,614✔
522
{
523
    return is_invalid(code_);
115,614✔
524
}
525

526
bool operation::is_push() const NOEXCEPT
1✔
527
{
528
    return is_push(code_);
1✔
529
}
530

531
bool operation::is_payload() const NOEXCEPT
×
532
{
533
    return is_payload(code_);
×
534
}
535

536
bool operation::is_counted() const NOEXCEPT
×
537
{
538
    return is_counted(code_);
×
539
}
540

541
bool operation::is_version() const NOEXCEPT
205✔
542
{
543
    return is_version(code_);
205✔
544
}
545

546
bool operation::is_numeric() const NOEXCEPT
×
547
{
548
    return is_numeric(code_);
×
549
}
550

551
bool operation::is_positive() const NOEXCEPT
×
552
{
553
    return is_positive(code_);
×
554
}
555

556
bool operation::is_reserved() const NOEXCEPT
×
557
{
558
    return is_reserved(code_);
×
559
}
560

561
bool operation::is_conditional() const NOEXCEPT
22,384✔
562
{
563
    return is_conditional(code_);
22,384✔
564
}
565

UNCOV
566
bool operation::is_relaxed_push() const NOEXCEPT
×
567
{
UNCOV
568
    return is_relaxed_push(code_);
×
569
}
570

571
bool operation::is_minimal_push() const NOEXCEPT
8✔
572
{
573
    return code_ == minimal_opcode_from_data(*data_);
8✔
574
}
575

576
bool operation::is_nominal_push() const NOEXCEPT
×
577
{
578
    return code_ == nominal_opcode_from_data(*data_);
×
579
}
580

581
bool operation::is_oversized() const NOEXCEPT
22,395✔
582
{
583
    // Rule max_push_data_size imposed by [0.3.6] soft fork.
584
    return data_->size() > max_push_data_size;
22,395✔
585
}
586

587
bool operation::is_underclaimed() const NOEXCEPT
4,470✔
588
{
589
    return data_->size() > operation::opcode_to_maximum_size(code_);
4,470✔
590
}
591

592
// ****************************************************************************
593
// CONSENSUS: An underflow is sized op-undersized data. This is valid as long
594
// as the operation is not executed. For example, coinbase input scripts.
595
// ****************************************************************************
596
bool operation::is_underflow() const NOEXCEPT
1,491,429✔
597
{
598
    return underflow_;
1,491,421✔
599
}
600

601
// JSON value convertors.
602
// ----------------------------------------------------------------------------
603

604
namespace json = boost::json;
605

606
// boost/json will soon have NOEXCEPT: github.com/boostorg/json/pull/636
607
BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
608

609
operation tag_invoke(json::value_to_tag<operation>,
1✔
610
    const json::value& value) NOEXCEPT
611
{
612
    return operation{ std::string(value.get_string().c_str()) };
1✔
613
}
614

615
void tag_invoke(json::value_from_tag, json::value& value,
2✔
616
    const operation& operation) NOEXCEPT
617
{
618
    value = operation.to_string(flags::all_rules);
2✔
619
}
2✔
620

621
BC_POP_WARNING()
622

623
operation::cptr tag_invoke(json::value_to_tag<operation::cptr>,
×
624
    const json::value& value) NOEXCEPT
625
{
626
    return to_shared(tag_invoke(json::value_to_tag<operation>{}, value));
×
627
}
628

629
// Shared pointer overload is required for navigation.
630
BC_PUSH_WARNING(SMART_PTR_NOT_NEEDED)
631
BC_PUSH_WARNING(NO_VALUE_OR_CONST_REF_SHARED_PTR)
632

633
void tag_invoke(json::value_from_tag tag, json::value& value,
×
634
    const operation::cptr& operation) NOEXCEPT
635
{
636
    tag_invoke(tag, value, *operation);
×
637
}
×
638

639
BC_POP_WARNING()
640
BC_POP_WARNING()
641

642
} // namespace chain
643
} // namespace system
644
} // 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

© 2025 Coveralls, Inc