feat: implement envelope tests

This commit is contained in:
Gaze
2024-05-21 11:27:28 +07:00
parent b3363c7983
commit eb65b8dcbc
4 changed files with 962 additions and 26 deletions

View File

@@ -38,7 +38,7 @@ func ParseEnvelopesFromTx(tx *wire.MsgTx) []*Envelope {
return envelopes
}
const protocolId = "ord"
var protocolId = []byte("ord")
func envelopesFromTapScript(tokenizer txscript.ScriptTokenizer, inputIndex int) []*Envelope {
envelopes := make([]*Envelope, 0)
@@ -71,7 +71,7 @@ func envelopeFromTokenizer(tokenizer txscript.ScriptTokenizer, inputIndex int, o
}
tokenizer.Next()
if !bytes.Equal(tokenizer.Data(), []byte(protocolId)) {
if !bytes.Equal(tokenizer.Data(), protocolId) {
return nil, tokenizer.Opcode() == txscript.OP_FALSE
}
@@ -149,19 +149,25 @@ func envelopeFromTokenizer(tokenizer txscript.ScriptTokenizer, inputIndex int, o
}
}
// incomplete envelope
if tokenizer.Done() {
if tokenizer.Done() && tokenizer.Opcode() != txscript.OP_ENDIF {
return nil, false
}
_, bodyIndex, ok := lo.FindIndexOf(payload, func(value []byte) bool {
return len(value) == 0
})
var fieldPayloads, bodyPayloads [][]byte
if ok {
fieldPayloads = payload[:]
} else {
// find body (empty data push in even index payload)
bodyIndex := -1
for i, value := range payload {
if i%2 == 0 && len(value) == 0 {
bodyIndex = i
break
}
}
var fieldPayloads [][]byte
var body []byte
if bodyIndex != -1 {
fieldPayloads = payload[:bodyIndex]
bodyPayloads = payload[bodyIndex+1:]
body = lo.Flatten(payload[bodyIndex+1:])
} else {
fieldPayloads = payload[:]
}
var incompleteField bool
@@ -203,8 +209,6 @@ func envelopeFromTokenizer(tokenizer txscript.ScriptTokenizer, inputIndex int, o
return key%2 == 0
})
body := lo.Flatten(bodyPayloads)
var delegate, parent *InscriptionId
inscriptionId, err := NewInscriptionIdFromString(string(rawDelegate))
if err == nil {
@@ -216,10 +220,14 @@ func envelopeFromTokenizer(tokenizer txscript.ScriptTokenizer, inputIndex int, o
}
var pointer *uint64
// if rawPointer fits in uint64
if len(rawPointer) <= 8 || lo.EveryBy(rawPointer[8:], func(value byte) bool {
// if rawPointer is not nil and fits in uint64
if rawPointer != nil && (len(rawPointer) <= 8 || lo.EveryBy(rawPointer[8:], func(value byte) bool {
return value != 0
}) {
})) {
// pad zero bytes to 8 bytes
if len(rawPointer) < 8 {
rawPointer = append(rawPointer, make([]byte, 8-len(rawPointer))...)
}
pointer = lo.ToPtr(binary.LittleEndian.Uint64(rawPointer))
}
@@ -252,6 +260,10 @@ func (fields Fields) Take(tag Tag) []byte {
if !ok {
return nil
}
if tag.IsChunked() {
delete(fields, tag)
return lo.Flatten(values)
} else {
first := values[0]
values = values[1:]
if len(values) == 0 {
@@ -260,11 +272,7 @@ func (fields Fields) Take(tag Tag) []byte {
fields[tag] = values
}
return first
}
func isDataPushOpCode(opCode byte) bool {
// includes OP_0, OP_DATA_1 to OP_DATA_75, OP_PUSHDATA1, OP_PUSHDATA2, OP_PUSHDATA4
return opCode <= txscript.OP_PUSHDATA4
}
}
func extractTapScript(witness [][]byte) (txscript.ScriptTokenizer, bool) {

View File

@@ -0,0 +1,741 @@
package ordinals
import (
"testing"
"github.com/Cleverse/go-utilities/utils"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
)
func TestParseEnvelopesFromTx(t *testing.T) {
testTx := func(t *testing.T, tx *wire.MsgTx, expected []*Envelope) {
t.Helper()
envelopes := ParseEnvelopesFromTx(tx)
assert.Equal(t, expected, envelopes)
}
testParseWitness := func(t *testing.T, tapScript []byte, expected []*Envelope) {
t.Helper()
tx := &wire.MsgTx{
Version: 2,
LockTime: 0,
TxIn: []*wire.TxIn{
{
Witness: wire.TxWitness{
tapScript,
{},
},
},
},
}
testTx(t, tx, expected)
}
testEnvelope := func(t *testing.T, payload [][]byte, expected []*Envelope) {
t.Helper()
builder := NewPushScriptBuilder().
AddOp(txscript.OP_FALSE).
AddOp(txscript.OP_IF)
for _, data := range payload {
builder.AddData(data)
}
builder.AddOp(txscript.OP_ENDIF)
script, err := builder.Script()
assert.NoError(t, err)
testParseWitness(
t,
script,
expected,
)
}
t.Run("empty_witness", func(t *testing.T) {
testTx(t, &wire.MsgTx{
Version: 2,
LockTime: 0,
TxIn: []*wire.TxIn{{
Witness: wire.TxWitness{},
}},
}, []*Envelope{})
})
t.Run("ignore_key_path_spends", func(t *testing.T) {
testTx(t, &wire.MsgTx{
Version: 2,
LockTime: 0,
TxIn: []*wire.TxIn{{
Witness: wire.TxWitness{
utils.Must(NewPushScriptBuilder().
AddOp(txscript.OP_FALSE).
AddOp(txscript.OP_IF).
AddData(protocolId).
AddOp(txscript.OP_ENDIF).
Script()),
},
}},
}, []*Envelope{})
})
t.Run("ignore_key_path_spends_with_annex", func(t *testing.T) {
testTx(t, &wire.MsgTx{
Version: 2,
LockTime: 0,
TxIn: []*wire.TxIn{{
Witness: wire.TxWitness{
utils.Must(NewPushScriptBuilder().
AddOp(txscript.OP_FALSE).
AddOp(txscript.OP_IF).
AddData(protocolId).
AddOp(txscript.OP_ENDIF).
Script()),
[]byte{txscript.TaprootAnnexTag},
},
}},
}, []*Envelope{})
})
t.Run("parse_from_tapscript", func(t *testing.T) {
testParseWitness(
t,
utils.Must(NewPushScriptBuilder().
AddOp(txscript.OP_FALSE).
AddOp(txscript.OP_IF).
AddData(protocolId).
AddOp(txscript.OP_ENDIF).
Script()),
[]*Envelope{{}},
)
})
t.Run("ignore_unparsable_scripts", func(t *testing.T) {
script := utils.Must(NewPushScriptBuilder().
AddOp(txscript.OP_FALSE).
AddOp(txscript.OP_IF).
AddData(protocolId).
AddOp(txscript.OP_ENDIF).
Script())
script = append(script, 0x01)
testParseWitness(
t,
script,
[]*Envelope{
{},
},
)
})
t.Run("no_inscription", func(t *testing.T) {
testParseWitness(
t,
utils.Must(NewPushScriptBuilder().
Script()),
[]*Envelope{},
)
})
t.Run("duplicate_field", func(t *testing.T) {
testEnvelope(
t,
[][]byte{
protocolId,
TagNop.Bytes(),
{},
TagNop.Bytes(),
{},
},
[]*Envelope{
{
DuplicateField: true,
},
},
)
})
t.Run("with_content_type", func(t *testing.T) {
testEnvelope(
t,
[][]byte{
protocolId,
TagContentType.Bytes(),
[]byte("text/plain;charset=utf-8"),
TagBody.Bytes(),
[]byte("ord"),
},
[]*Envelope{
{
Inscription: Inscription{
Content: []byte("ord"),
ContentType: "text/plain;charset=utf-8",
},
},
},
)
})
t.Run("with_content_encoding", func(t *testing.T) {
testEnvelope(
t,
[][]byte{
protocolId,
TagContentType.Bytes(),
[]byte("text/plain;charset=utf-8"),
TagContentEncoding.Bytes(),
[]byte("br"),
TagBody.Bytes(),
[]byte("ord"),
},
[]*Envelope{
{
Inscription: Inscription{
Content: []byte("ord"),
ContentType: "text/plain;charset=utf-8",
ContentEncoding: "br",
},
},
},
)
})
t.Run("with_unknown_tag", func(t *testing.T) {
testEnvelope(
t,
[][]byte{
protocolId,
TagContentType.Bytes(),
[]byte("text/plain;charset=utf-8"),
TagNop.Bytes(),
[]byte("bar"),
TagBody.Bytes(),
[]byte("ord"),
},
[]*Envelope{
{
Inscription: Inscription{
Content: []byte("ord"),
ContentType: "text/plain;charset=utf-8",
},
},
},
)
})
t.Run("no_body", func(t *testing.T) {
testEnvelope(
t,
[][]byte{
protocolId,
TagContentType.Bytes(),
[]byte("text/plain;charset=utf-8"),
},
[]*Envelope{
{
Inscription: Inscription{
ContentType: "text/plain;charset=utf-8",
},
},
},
)
})
t.Run("no_content_type", func(t *testing.T) {
testEnvelope(
t,
[][]byte{
protocolId,
TagBody.Bytes(),
[]byte("foo"),
},
[]*Envelope{
{
Inscription: Inscription{
Content: []byte("foo"),
},
},
},
)
})
t.Run("valid_body_in_multiple_pushes", func(t *testing.T) {
testEnvelope(
t,
[][]byte{
protocolId,
TagContentType.Bytes(),
[]byte("text/plain;charset=utf-8"),
TagBody.Bytes(),
[]byte("foo"),
[]byte("bar"),
},
[]*Envelope{
{
Inscription: Inscription{
Content: []byte("foobar"),
ContentType: "text/plain;charset=utf-8",
},
},
},
)
})
t.Run("valid_body_in_zero_pushes", func(t *testing.T) {
testEnvelope(
t,
[][]byte{
protocolId,
TagContentType.Bytes(),
[]byte("text/plain;charset=utf-8"),
TagBody.Bytes(),
},
[]*Envelope{
{
Inscription: Inscription{
Content: []byte(""),
ContentType: "text/plain;charset=utf-8",
},
},
},
)
})
t.Run("valid_body_in_multiple_empty_pushes", func(t *testing.T) {
testEnvelope(
t,
[][]byte{
protocolId,
TagContentType.Bytes(),
[]byte("text/plain;charset=utf-8"),
TagBody.Bytes(),
{},
{},
{},
{},
{},
{},
{},
},
[]*Envelope{
{
Inscription: Inscription{
Content: []byte(""),
ContentType: "text/plain;charset=utf-8",
},
},
},
)
})
t.Run("valid_ignore_trailing", func(t *testing.T) {
testParseWitness(
t,
utils.Must(NewPushScriptBuilder().
AddOp(txscript.OP_FALSE).
AddOp(txscript.OP_IF).
AddData(protocolId).
AddData(TagContentType.Bytes()).
AddData([]byte("text/plain;charset=utf-8")).
AddData(TagBody.Bytes()).
AddData([]byte("ord")).
AddOp(txscript.OP_ENDIF).
AddOp(txscript.OP_CHECKSIG).
Script()),
[]*Envelope{
{
Inscription: Inscription{
Content: []byte("ord"),
ContentType: "text/plain;charset=utf-8",
},
},
},
)
})
t.Run("valid_ignore_preceding", func(t *testing.T) {
testParseWitness(
t,
utils.Must(NewPushScriptBuilder().
AddOp(txscript.OP_CHECKSIG).
AddOp(txscript.OP_FALSE).
AddOp(txscript.OP_IF).
AddData(protocolId).
AddData(TagContentType.Bytes()).
AddData([]byte("text/plain;charset=utf-8")).
AddData(TagBody.Bytes()).
AddData([]byte("ord")).
AddOp(txscript.OP_ENDIF).
Script()),
[]*Envelope{
{
Inscription: Inscription{
Content: []byte("ord"),
ContentType: "text/plain;charset=utf-8",
},
},
},
)
})
t.Run("multiple_inscriptions_in_a_single_witness", func(t *testing.T) {
testParseWitness(
t,
utils.Must(NewPushScriptBuilder().
AddOp(txscript.OP_FALSE).
AddOp(txscript.OP_IF).
AddData(protocolId).
AddData(TagContentType.Bytes()).
AddData([]byte("text/plain;charset=utf-8")).
AddData(TagBody.Bytes()).
AddData([]byte("foo")).
AddOp(txscript.OP_ENDIF).
AddOp(txscript.OP_FALSE).
AddOp(txscript.OP_IF).
AddData(protocolId).
AddData(TagContentType.Bytes()).
AddData([]byte("text/plain;charset=utf-8")).
AddData(TagBody.Bytes()).
AddData([]byte("bar")).
AddOp(txscript.OP_ENDIF).
Script()),
[]*Envelope{
{
Inscription: Inscription{
Content: []byte("foo"),
ContentType: "text/plain;charset=utf-8",
},
},
{
Inscription: Inscription{
Content: []byte("bar"),
ContentType: "text/plain;charset=utf-8",
},
Offset: 1,
},
},
)
})
t.Run("invalid_utf8_does_not_render_inscription_invalid", func(t *testing.T) {
testEnvelope(
t,
[][]byte{
protocolId,
TagContentType.Bytes(),
[]byte("text/plain;charset=utf-8"),
TagBody.Bytes(),
{0b10000000},
},
[]*Envelope{
{
Inscription: Inscription{
Content: []byte{0b10000000},
ContentType: "text/plain;charset=utf-8",
},
},
},
)
})
t.Run("no_endif", func(t *testing.T) {
testParseWitness(
t,
utils.Must(NewPushScriptBuilder().
AddOp(txscript.OP_FALSE).
AddOp(txscript.OP_IF).
AddData(protocolId).
Script()),
[]*Envelope{},
)
})
t.Run("no_op_false", func(t *testing.T) {
testParseWitness(
t,
utils.Must(NewPushScriptBuilder().
AddOp(txscript.OP_IF).
AddData(protocolId).
AddOp(txscript.OP_ENDIF).
Script()),
[]*Envelope{},
)
})
t.Run("empty_envelope", func(t *testing.T) {
testEnvelope(
t,
[][]byte{},
[]*Envelope{},
)
})
t.Run("wrong_protocol_identifier", func(t *testing.T) {
testEnvelope(
t,
[][]byte{
[]byte("foo"),
},
[]*Envelope{},
)
})
t.Run("extract_from_second_input", func(t *testing.T) {
testTx(
t,
&wire.MsgTx{
Version: 2,
LockTime: 0,
TxIn: []*wire.TxIn{{}, {
Witness: wire.TxWitness{
utils.Must(NewPushScriptBuilder().
AddOp(txscript.OP_FALSE).
AddOp(txscript.OP_IF).
AddData(protocolId).
AddData(TagContentType.Bytes()).
AddData([]byte("text/plain;charset=utf-8")).
AddData(TagBody.Bytes()).
AddData([]byte("ord")).
AddOp(txscript.OP_ENDIF).
Script(),
),
{},
},
}},
},
[]*Envelope{
{
Inscription: Inscription{
Content: []byte("ord"),
ContentType: "text/plain;charset=utf-8",
},
InputIndex: 1,
},
},
)
})
t.Run("inscribe_png", func(t *testing.T) {
testEnvelope(
t,
[][]byte{
protocolId,
TagContentType.Bytes(),
[]byte("image/png"),
TagBody.Bytes(),
{0x01, 0x02, 0x03},
},
[]*Envelope{
{
Inscription: Inscription{
Content: []byte{0x01, 0x02, 0x03},
ContentType: "image/png",
},
},
},
)
})
t.Run("unknown_odd_fields", func(t *testing.T) {
testEnvelope(
t,
[][]byte{
protocolId,
TagNop.Bytes(),
{0x00},
},
[]*Envelope{
{
Inscription: Inscription{},
},
},
)
})
t.Run("unknown_even_fields", func(t *testing.T) {
testEnvelope(
t,
[][]byte{
protocolId,
TagUnbound.Bytes(),
{0x00},
},
[]*Envelope{
{
Inscription: Inscription{},
UnrecognizedEvenField: true,
},
},
)
})
t.Run("pointer_field_is_recognized", func(t *testing.T) {
testEnvelope(
t,
[][]byte{
protocolId,
TagPointer.Bytes(),
{0x01},
},
[]*Envelope{
{
Inscription: Inscription{
Pointer: lo.ToPtr(uint64(1)),
},
},
},
)
})
t.Run("duplicate_pointer_field_makes_inscription_unbound", func(t *testing.T) {
testEnvelope(
t,
[][]byte{
protocolId,
TagPointer.Bytes(),
{0x01},
TagPointer.Bytes(),
{0x00},
},
[]*Envelope{
{
Inscription: Inscription{
Pointer: lo.ToPtr(uint64(1)),
},
DuplicateField: true,
UnrecognizedEvenField: true,
},
},
)
})
t.Run("incomplete_field", func(t *testing.T) {
testEnvelope(
t,
[][]byte{
protocolId,
TagNop.Bytes(),
},
[]*Envelope{
{
Inscription: Inscription{},
IncompleteField: true,
},
},
)
})
t.Run("metadata_is_parsed_correctly", func(t *testing.T) {
testEnvelope(
t,
[][]byte{
protocolId,
TagMetadata.Bytes(),
{},
},
[]*Envelope{
{
Inscription: Inscription{
Metadata: []byte{},
},
},
},
)
})
t.Run("metadata_is_parsed_correctly_from_chunks", func(t *testing.T) {
testEnvelope(
t,
[][]byte{
protocolId,
TagMetadata.Bytes(),
{0x00},
TagMetadata.Bytes(),
{0x01},
},
[]*Envelope{
{
Inscription: Inscription{
Metadata: []byte{0x00, 0x01},
},
DuplicateField: true,
},
},
)
})
t.Run("pushnum_opcodes_are_parsed_correctly", func(t *testing.T) {
pushNumOpCodes := map[byte][]byte{
txscript.OP_1NEGATE: {0x81},
txscript.OP_1: {0x01},
txscript.OP_2: {0x02},
txscript.OP_3: {0x03},
txscript.OP_4: {0x04},
txscript.OP_5: {0x05},
txscript.OP_6: {0x06},
txscript.OP_7: {0x07},
txscript.OP_8: {0x08},
txscript.OP_9: {0x09},
txscript.OP_10: {0x10},
txscript.OP_11: {0x11},
txscript.OP_12: {0x12},
txscript.OP_13: {0x13},
txscript.OP_14: {0x14},
txscript.OP_15: {0x15},
txscript.OP_16: {0x16},
}
for opCode, value := range pushNumOpCodes {
script := utils.Must(NewPushScriptBuilder().
AddOp(txscript.OP_FALSE).
AddOp(txscript.OP_IF).
AddData(protocolId).
AddData(TagBody.Bytes()).
AddOp(opCode).
AddOp(txscript.OP_ENDIF).
Script())
testParseWitness(
t,
script,
[]*Envelope{
{
Inscription: Inscription{
Content: value,
},
PushNum: true,
},
},
)
}
})
t.Run("stuttering", func(t *testing.T) {
script := utils.Must(NewPushScriptBuilder().
AddOp(txscript.OP_FALSE).
AddOp(txscript.OP_FALSE).
AddOp(txscript.OP_IF).
AddData(protocolId).
AddOp(txscript.OP_ENDIF).
Script())
testParseWitness(
t,
script,
[]*Envelope{
{
Inscription: Inscription{},
Stutter: true,
},
},
)
script = utils.Must(NewPushScriptBuilder().
AddOp(txscript.OP_FALSE).
AddOp(txscript.OP_IF).
AddOp(txscript.OP_FALSE).
AddOp(txscript.OP_IF).
AddOp(txscript.OP_FALSE).
AddOp(txscript.OP_IF).
AddData(protocolId).
AddOp(txscript.OP_ENDIF).
Script())
testParseWitness(
t,
script,
[]*Envelope{
{
Inscription: Inscription{},
Stutter: true,
},
},
)
script = utils.Must(NewPushScriptBuilder().
AddOp(txscript.OP_FALSE).
AddOp(txscript.OP_FALSE).
AddOp(txscript.OP_AND).
AddOp(txscript.OP_FALSE).
AddOp(txscript.OP_IF).
AddData(protocolId).
AddOp(txscript.OP_ENDIF).
Script())
testParseWitness(
t,
script,
[]*Envelope{
{
Inscription: Inscription{},
Stutter: false,
},
},
)
})
}

View File

@@ -0,0 +1,170 @@
package ordinals
import (
"encoding/binary"
"fmt"
"github.com/btcsuite/btcd/txscript"
)
// PushScriptBuilder is a helper to build scripts that requires data pushes to use OP_PUSHDATA* or OP_DATA_* opcodes only.
// Empty data pushes are still encoded as OP_0.
type PushScriptBuilder struct {
script []byte
err error
}
func NewPushScriptBuilder() *PushScriptBuilder {
return &PushScriptBuilder{}
}
// canonicalDataSize returns the number of bytes the canonical encoding of the
// data will take.
func canonicalDataSize(data []byte) int {
dataLen := len(data)
// When the data consists of a single number that can be represented
// by one of the "small integer" opcodes, that opcode will be instead
// of a data push opcode followed by the number.
if dataLen == 0 {
return 1
}
if dataLen < txscript.OP_PUSHDATA1 {
return 1 + dataLen
} else if dataLen <= 0xff {
return 2 + dataLen
} else if dataLen <= 0xffff {
return 3 + dataLen
}
return 5 + dataLen
}
func pushDataToBytes(data []byte) []byte {
if len(data) == 0 {
return []byte{txscript.OP_0}
}
script := make([]byte, 0)
dataLen := len(data)
if dataLen < txscript.OP_PUSHDATA1 {
script = append(script, byte(txscript.OP_DATA_1-1+dataLen))
} else if dataLen <= 0xff {
script = append(script, txscript.OP_PUSHDATA1, byte(dataLen))
} else if dataLen <= 0xffff {
buf := make([]byte, 2)
binary.LittleEndian.PutUint16(buf, uint16(dataLen))
script = append(script, txscript.OP_PUSHDATA2)
script = append(script, buf...)
} else {
buf := make([]byte, 4)
binary.LittleEndian.PutUint32(buf, uint32(dataLen))
script = append(script, txscript.OP_PUSHDATA4)
script = append(script, buf...)
}
// Append the actual data.
script = append(script, data...)
return script
}
// AddData pushes the passed data to the end of the script. It automatically
// chooses canonical opcodes depending on the length of the data. A zero length
// buffer will lead to a push of empty data onto the stack (OP_0) and any push
// of data greater than MaxScriptElementSize will not modify the script since
// that is not allowed by the script engine. Also, the script will not be
// modified if pushing the data would cause the script to exceed the maximum
// allowed script engine size.
func (b *PushScriptBuilder) AddData(data []byte) *PushScriptBuilder {
if b.err != nil {
return b
}
// Pushes that would cause the script to exceed the largest allowed
// script size would result in a non-canonical script.
dataSize := canonicalDataSize(data)
if len(b.script)+dataSize > txscript.MaxScriptSize {
str := fmt.Sprintf("adding %d bytes of data would exceed the "+
"maximum allowed canonical script length of %d",
dataSize, txscript.MaxScriptSize)
b.err = txscript.ErrScriptNotCanonical(str)
return b
}
// Pushes larger than the max script element size would result in a
// script that is not canonical.
dataLen := len(data)
if dataLen > txscript.MaxScriptElementSize {
str := fmt.Sprintf("adding a data element of %d bytes would "+
"exceed the maximum allowed script element size of %d",
dataLen, txscript.MaxScriptElementSize)
b.err = txscript.ErrScriptNotCanonical(str)
return b
}
b.script = append(b.script, pushDataToBytes(data)...)
return b
}
// AddFullData should not typically be used by ordinary users as it does not
// include the checks which prevent data pushes larger than the maximum allowed
// sizes which leads to scripts that can't be executed. This is provided for
// testing purposes such as regression tests where sizes are intentionally made
// larger than allowed.
//
// Use AddData instead.
func (b *PushScriptBuilder) AddFullData(data []byte) *PushScriptBuilder {
if b.err != nil {
return b
}
b.script = append(b.script, pushDataToBytes(data)...)
return b
}
// AddOp pushes the passed opcode to the end of the script. The script will not
// be modified if pushing the opcode would cause the script to exceed the
// maximum allowed script engine size.
func (b *PushScriptBuilder) AddOp(opcode byte) *PushScriptBuilder {
if b.err != nil {
return b
}
// Pushes that would cause the script to exceed the largest allowed
// script size would result in a non-canonical script.
if len(b.script)+1 > txscript.MaxScriptSize {
str := fmt.Sprintf("adding an opcode would exceed the maximum "+
"allowed canonical script length of %d", txscript.MaxScriptSize)
b.err = txscript.ErrScriptNotCanonical(str)
return b
}
b.script = append(b.script, opcode)
return b
}
// AddOps pushes the passed opcodes to the end of the script. The script will
// not be modified if pushing the opcodes would cause the script to exceed the
// maximum allowed script engine size.
func (b *PushScriptBuilder) AddOps(opcodes []byte) *PushScriptBuilder {
if b.err != nil {
return b
}
// Pushes that would cause the script to exceed the largest allowed
// script size would result in a non-canonical script.
if len(b.script)+len(opcodes) > txscript.MaxScriptSize {
str := fmt.Sprintf("adding opcodes would exceed the maximum "+
"allowed canonical script length of %d", txscript.MaxScriptSize)
b.err = txscript.ErrScriptNotCanonical(str)
return b
}
b.script = append(b.script, opcodes...)
return b
}
// Script returns the currently built script. When any errors occurred while
// building the script, the script will be returned up the point of the first
// error along with the error.
func (b *PushScriptBuilder) Script() ([]byte, error) {
return b.script, b.err
}

View File

@@ -4,6 +4,7 @@ package ordinals
type Tag uint8
var (
TagBody = Tag(0)
TagPointer = Tag(2)
// TagUnbound is unrecognized
TagUnbound = Tag(66)
@@ -34,6 +35,22 @@ func (t Tag) IsValid() bool {
return ok
}
var chunkedTags = map[Tag]struct{}{
TagMetadata: {},
}
func (t Tag) IsChunked() bool {
_, ok := chunkedTags[t]
return ok
}
func (t Tag) Bytes() []byte {
if t == TagBody {
return []byte{} // body tag is empty data push
}
return []byte{byte(t)}
}
func ParseTag(input interface{}) (Tag, error) {
switch input := input.(type) {
case Tag: