├── .dockerignore ├── .github ├── CODEOWNERS ├── assets │ ├── gOuroboros-illustration.png │ ├── gOuroboros-logo-with-text-horizontal.png │ └── gOuroboros-logo-with-text.png ├── dependabot.yml └── workflows │ ├── conventional-commits.yml │ ├── go-test.yml │ ├── golangci-lint.yml │ └── publish.yml ├── .gitignore ├── .gitmodules ├── .golangci.yml ├── LICENSE ├── Makefile ├── README.md ├── cbor ├── bytestring.go ├── bytestring_test.go ├── cbor.go ├── decode.go ├── decode_test.go ├── encode.go ├── encode_test.go ├── tags.go ├── tags_test.go ├── value.go └── value_test.go ├── cmd ├── common │ ├── cmdline.go │ └── connect.go ├── gouroboros │ ├── chainsync.go │ ├── localtxsubmission.go │ ├── main.go │ ├── mem_usage.go │ ├── query.go │ └── server.go └── tx-submission │ └── main.go ├── connection.go ├── connection └── id.go ├── connection_options.go ├── connection_test.go ├── go.mod ├── go.sum ├── internal └── test │ ├── helpers.go │ └── ledger │ └── ledger.go ├── ledger ├── allegra.go ├── allegra │ ├── allegra.go │ ├── errors.go │ ├── pparams.go │ ├── pparams_test.go │ ├── rules.go │ └── rules_test.go ├── alonzo.go ├── alonzo │ ├── alonzo.go │ ├── errors.go │ ├── genesis.go │ ├── genesis_test.go │ ├── pparams.go │ ├── pparams_test.go │ ├── rules.go │ └── rules_test.go ├── babbage.go ├── babbage │ ├── babbage.go │ ├── babbage_test.go │ ├── errors.go │ ├── pparams.go │ ├── pparams_test.go │ ├── rules.go │ └── rules_test.go ├── block.go ├── block_test.go ├── byron.go ├── byron │ ├── byron.go │ ├── genesis.go │ └── genesis_test.go ├── common │ ├── address.go │ ├── address_test.go │ ├── block.go │ ├── certs.go │ ├── common.go │ ├── common_test.go │ ├── credentials.go │ ├── era.go │ ├── era_test.go │ ├── genesis.go │ ├── gov.go │ ├── native_script.go │ ├── nonce.go │ ├── nonce_test.go │ ├── pparams.go │ ├── rules.go │ ├── state.go │ ├── tx.go │ ├── vrf.go │ └── witness.go ├── compat.go ├── conway.go ├── conway │ ├── conway.go │ ├── errors.go │ ├── genesis.go │ ├── genesis_test.go │ ├── pparams.go │ ├── pparams_test.go │ ├── rules.go │ └── rules_test.go ├── era.go ├── error.go ├── mary.go ├── mary │ ├── errors.go │ ├── mary.go │ ├── mary_test.go │ ├── pparams.go │ ├── pparams_test.go │ ├── rules.go │ └── rules_test.go ├── shelley.go ├── shelley │ ├── errors.go │ ├── genesis.go │ ├── genesis_test.go │ ├── pparams.go │ ├── pparams_test.go │ ├── rules.go │ ├── rules_test.go │ └── shelley.go ├── tx.go ├── tx_test.go ├── verify_block.go ├── verify_block_body.go ├── verify_block_test.go ├── verify_kes.go └── verify_vrf.go ├── muxer ├── muxer.go └── segment.go ├── networks.go ├── protocol ├── blockfetch │ ├── blockfetch.go │ ├── client.go │ ├── client_test.go │ ├── messages.go │ ├── messages_test.go │ └── server.go ├── chainsync │ ├── chainsync.go │ ├── client.go │ ├── client_test.go │ ├── error.go │ ├── messages.go │ ├── messages_test.go │ ├── server.go │ ├── server_test.go │ ├── testdata │ │ ├── byron_ebb_testnet_8f8602837f7c6f8b8867dd1cbc1842cf51a27eaed2c70ef48325d00f8efb320f.hex │ │ ├── byron_main_block_testnet_f38aa5e8cf0b47d1ffa8b2385aa2d43882282db2ffd5ac0e3dadec1a6f2ecf08.hex │ │ ├── rollforward_ntc_byron_ebb_testnet_8f8602837f7c6f8b8867dd1cbc1842cf51a27eaed2c70ef48325d00f8efb320f.hex │ │ ├── rollforward_ntc_byron_main_block_testnet_f38aa5e8cf0b47d1ffa8b2385aa2d43882282db2ffd5ac0e3dadec1a6f2ecf08.hex │ │ ├── rollforward_ntc_shelley_block_testnet_02b1c561715da9e540411123a6135ee319b02f60b9a11a603d3305556c04329f.hex │ │ ├── rollforward_ntn_byron_ebb_testnet_8f8602837f7c6f8b8867dd1cbc1842cf51a27eaed2c70ef48325d00f8efb320f.hex │ │ ├── rollforward_ntn_shelley_block_testnet_02b1c561715da9e540411123a6135ee319b02f60b9a11a603d3305556c04329f.hex │ │ └── shelley_block_testnet_02b1c561715da9e540411123a6135ee319b02f60b9a11a603d3305556c04329f.hex │ └── wrappers.go ├── common │ └── types.go ├── error.go ├── handshake │ ├── client.go │ ├── client_test.go │ ├── handshake.go │ ├── messages.go │ ├── messages_test.go │ ├── server.go │ └── server_test.go ├── keepalive │ ├── client.go │ ├── client_test.go │ ├── keepalive.go │ ├── messages.go │ ├── messages_test.go │ └── server.go ├── localstatequery │ ├── client.go │ ├── client_test.go │ ├── error.go │ ├── localstatequery.go │ ├── messages.go │ ├── messages_test.go │ ├── queries.go │ └── server.go ├── localtxmonitor │ ├── client.go │ ├── client_test.go │ ├── localtxmonitor.go │ ├── messages.go │ ├── messages_test.go │ └── server.go ├── localtxsubmission │ ├── client.go │ ├── client_test.go │ ├── error.go │ ├── localtxsubmission.go │ ├── messages.go │ ├── messages_test.go │ └── server.go ├── message.go ├── peersharing │ ├── client.go │ ├── messages.go │ ├── messages_test.go │ ├── peersharing.go │ └── server.go ├── protocol.go ├── state.go ├── txsubmission │ ├── client.go │ ├── messages.go │ ├── messages_test.go │ ├── server.go │ └── txsubmission.go ├── versiondata.go └── versions.go ├── scripts └── cbor_dump_formatter.py └── utils └── debug.go /.dockerignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | .github/ 3 | Dockerfile 4 | README.md 5 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Blink Labs 2 | # 3 | * @blinklabs-io/core 4 | *.md @blinklabs-io/core @blinklabs-io/docs @blinklabs-io/pms 5 | LICENSE @blinklabs-io/core @blinklabs-io/pms 6 | -------------------------------------------------------------------------------- /.github/assets/gOuroboros-illustration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blinklabs-io/gouroboros/f818b7a217b250ca859bc2b092e48bf66f5e3e40/.github/assets/gOuroboros-illustration.png -------------------------------------------------------------------------------- /.github/assets/gOuroboros-logo-with-text-horizontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blinklabs-io/gouroboros/f818b7a217b250ca859bc2b092e48bf66f5e3e40/.github/assets/gOuroboros-logo-with-text-horizontal.png -------------------------------------------------------------------------------- /.github/assets/gOuroboros-logo-with-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blinklabs-io/gouroboros/f818b7a217b250ca859bc2b092e48bf66f5e3e40/.github/assets/gOuroboros-logo-with-text.png -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | - package-ecosystem: "gomod" 13 | directory: "/" 14 | schedule: 15 | interval: "weekly" 16 | -------------------------------------------------------------------------------- /.github/workflows/conventional-commits.yml: -------------------------------------------------------------------------------- 1 | # The below is pulled from upstream and slightly modified 2 | # https://github.com/webiny/action-conventional-commits/blob/master/README.md#usage 3 | 4 | name: Conventional Commits 5 | 6 | on: 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | name: Conventional Commits 12 | runs-on: ubuntu-latest 13 | permissions: 14 | contents: read 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: webiny/action-conventional-commits@v1.3.0 18 | -------------------------------------------------------------------------------- /.github/workflows/go-test.yml: -------------------------------------------------------------------------------- 1 | name: go-test 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | branches: 8 | - main 9 | pull_request: 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | go-test: 16 | name: go-test 17 | strategy: 18 | matrix: 19 | go-version: [1.23.x, 1.24.x] 20 | platform: [ubuntu-latest] 21 | runs-on: ${{ matrix.platform }} 22 | steps: 23 | - uses: actions/checkout@v4 24 | with: 25 | submodules: true 26 | - uses: actions/setup-go@v5 27 | with: 28 | go-version: ${{ matrix.go-version }} 29 | - name: go-test 30 | run: go test ./... 31 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - main 8 | pull_request: 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | golangci: 15 | name: lint 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: actions/setup-go@v5 20 | with: 21 | go-version: 1.23.x 22 | - name: golangci-lint 23 | uses: golangci/golangci-lint-action@v8 24 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*.*.*' 7 | 8 | concurrency: ${{ github.ref }} 9 | 10 | jobs: 11 | create-release: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | contents: write 15 | outputs: 16 | RELEASE_ID: ${{ steps.create-release.outputs.result }} 17 | steps: 18 | - run: "echo \"RELEASE_TAG=${GITHUB_REF#refs/tags/}\" >> $GITHUB_ENV" 19 | - uses: actions/github-script@v7 20 | id: create-release 21 | with: 22 | github-token: ${{ secrets.GITHUB_TOKEN }} 23 | result-encoding: string 24 | script: | 25 | try { 26 | const response = await github.rest.repos.createRelease({ 27 | generate_release_notes: true, 28 | name: process.env.RELEASE_TAG, 29 | owner: context.repo.owner, 30 | prerelease: false, 31 | repo: context.repo.repo, 32 | tag_name: process.env.RELEASE_TAG, 33 | }); 34 | 35 | return response.data.id; 36 | } catch (error) { 37 | core.setFailed(error.message); 38 | } 39 | 40 | # This updates the documentation on pkg.go.dev and the latest version available via the Go module proxy 41 | - name: Pull new module version 42 | uses: andrewslotin/go-proxy-pull-action@v1.3.0 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | /gouroboros 8 | /tx-submission 9 | 10 | # Test binary, built with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Dependency directories (remove the comment below to include it) 17 | # vendor/ 18 | 19 | # Temporary dir for testing 20 | /tmp/ 21 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "cardano-blueprint"] 2 | path = internal/test/cardano-blueprint 3 | url = git@github.com:cardano-scaling/cardano-blueprint 4 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | run: 3 | issues-exit-code: 1 4 | tests: false 5 | linters: 6 | enable: 7 | - asciicheck 8 | - bidichk 9 | - bodyclose 10 | - contextcheck 11 | - copyloopvar 12 | - durationcheck 13 | - errchkjson 14 | - errorlint 15 | - exhaustive 16 | - fatcontext 17 | - gocheckcompilerdirectives 18 | - gochecksumtype 19 | - gomodguard 20 | - gosec 21 | - gosmopolitan 22 | - loggercheck 23 | - makezero 24 | - musttag 25 | - nilerr 26 | - nilnesserr 27 | - noctx 28 | - perfsprint 29 | - prealloc 30 | - protogetter 31 | - reassign 32 | - rowserrcheck 33 | - spancheck 34 | - sqlclosecheck 35 | - testifylint 36 | - usestdlibvars 37 | - whitespace 38 | - zerologlint 39 | disable: 40 | - asasalint 41 | - depguard 42 | - recvcheck 43 | - unparam 44 | exclusions: 45 | generated: lax 46 | presets: 47 | - comments 48 | - common-false-positives 49 | - legacy 50 | - std-error-handling 51 | paths: 52 | - docs 53 | - third_party$ 54 | - builtin$ 55 | - examples$ 56 | issues: 57 | max-issues-per-linter: 0 58 | max-same-issues: 0 59 | formatters: 60 | enable: 61 | - gci 62 | - gofmt 63 | - gofumpt 64 | - goimports 65 | exclusions: 66 | generated: lax 67 | paths: 68 | - docs 69 | - third_party$ 70 | - builtin$ 71 | - examples$ 72 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Determine root directory 2 | ROOT_DIR=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) 3 | 4 | # Gather all .go files for use in dependencies below 5 | GO_FILES=$(shell find $(ROOT_DIR) -name '*.go') 6 | 7 | # Gather list of expected binaries 8 | BINARIES=$(shell cd $(ROOT_DIR)/cmd && ls -1 | grep -v ^common) 9 | 10 | .PHONY: build mod-tidy clean test 11 | 12 | # Alias for building program binary 13 | build: $(BINARIES) 14 | 15 | mod-tidy: 16 | # Needed to fetch new dependencies and add them to go.mod 17 | go mod tidy 18 | 19 | clean: 20 | rm -f $(BINARIES) 21 | 22 | format: 23 | go fmt ./... 24 | gofmt -s -w $(GO_FILES) 25 | 26 | golines: 27 | golines -w --ignore-generated --chain-split-dots --max-len=80 --reformat-tags . 28 | 29 | test: mod-tidy 30 | go test -v -race ./... 31 | 32 | # Build our program binaries 33 | # Depends on GO_FILES to determine when rebuild is needed 34 | $(BINARIES): mod-tidy $(GO_FILES) 35 | go build -o $(@) ./cmd/$(@) 36 | -------------------------------------------------------------------------------- /cbor/bytestring.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cbor 16 | 17 | import ( 18 | "encoding/hex" 19 | "encoding/json" 20 | 21 | _cbor "github.com/fxamacker/cbor/v2" 22 | ) 23 | 24 | // Wrapper for bytestrings that allows them to be used as keys for a map 25 | // This was originally a full implementation, but now it just extends the upstream 26 | // type 27 | type ByteString struct { 28 | _cbor.ByteString 29 | } 30 | 31 | func NewByteString(data []byte) ByteString { 32 | bs := ByteString{ByteString: _cbor.ByteString(data)} 33 | return bs 34 | } 35 | 36 | // String returns a hex-encoded representation of the bytestring 37 | func (bs ByteString) String() string { 38 | return hex.EncodeToString(bs.Bytes()) 39 | } 40 | 41 | // MarshalJSON converts ByteString to a JSON-compatible string 42 | func (bs ByteString) MarshalJSON() ([]byte, error) { 43 | return json.Marshal(bs.String()) 44 | } 45 | -------------------------------------------------------------------------------- /cbor/bytestring_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package cbor 15 | 16 | import ( 17 | "encoding/json" 18 | "testing" 19 | ) 20 | 21 | // Test the String method to ensure it properly converts ByteString to hex. 22 | func TestByteString_String(t *testing.T) { 23 | data := []byte("blinklabs") // "blinklabs" as bytes 24 | bs := NewByteString(data) 25 | 26 | expected := "626c696e6b6c616273" // "blinklabs" in hex 27 | actual := bs.String() 28 | 29 | if actual != expected { 30 | t.Errorf("expected %s but got %s", expected, actual) 31 | } 32 | } 33 | 34 | // Test the MarshalJSON method to ensure it properly marshals ByteString to JSON as hex. 35 | func TestByteString_MarshalJSON(t *testing.T) { 36 | data := []byte("blinklabs") // "blinklabs" as bytes 37 | bs := NewByteString(data) 38 | 39 | jsonData, err := json.Marshal(bs) 40 | if err != nil { 41 | t.Fatalf("failed to marshal ByteString: %v", err) 42 | } 43 | 44 | // Expected JSON result, hex-encoded string 45 | expectedJSON := `"626c696e6b6c616273"` // "blinklabs" in hex 46 | 47 | if string(jsonData) != expectedJSON { 48 | t.Errorf("expected %s but got %s", expectedJSON, string(jsonData)) 49 | } 50 | } 51 | 52 | // Test NewByteString to ensure it properly wraps the byte slice 53 | func TestNewByteString(t *testing.T) { 54 | data := []byte{0x41, 0x42, 0x43} // "ABC" in hex 55 | bs := NewByteString(data) 56 | 57 | if string(bs.Bytes()) != "ABC" { 58 | t.Errorf("expected ABC but got %s", string(bs.Bytes())) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /cbor/cbor.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cbor 16 | 17 | import ( 18 | _cbor "github.com/fxamacker/cbor/v2" 19 | ) 20 | 21 | const ( 22 | CborTypeByteString uint8 = 0x40 23 | CborTypeTextString uint8 = 0x60 24 | CborTypeArray uint8 = 0x80 25 | CborTypeMap uint8 = 0xa0 26 | CborTypeTag uint8 = 0xc0 27 | 28 | // Only the top 3 bytes are used to specify the type 29 | CborTypeMask uint8 = 0xe0 30 | 31 | // Max value able to be stored in a single byte without type prefix 32 | CborMaxUintSimple uint8 = 0x17 33 | ) 34 | 35 | // Create an alias for RawMessage for convenience 36 | type RawMessage = _cbor.RawMessage 37 | 38 | // Alias for Tag for convenience 39 | type ( 40 | Tag = _cbor.Tag 41 | RawTag = _cbor.RawTag 42 | ) 43 | 44 | // Useful for embedding and easier to remember 45 | type StructAsArray struct { 46 | // Tells the CBOR decoder to convert to/from a struct and a CBOR array 47 | _ struct{} `cbor:",toarray"` 48 | } 49 | 50 | type DecodeStoreCborInterface interface { 51 | Cbor() []byte 52 | SetCbor([]byte) 53 | } 54 | 55 | type DecodeStoreCbor struct { 56 | cborData []byte 57 | } 58 | 59 | func (d *DecodeStoreCbor) SetCbor(cborData []byte) { 60 | if cborData == nil { 61 | d.cborData = nil 62 | return 63 | } 64 | d.cborData = make([]byte, len(cborData)) 65 | copy(d.cborData, cborData) 66 | } 67 | 68 | // Cbor returns the original CBOR for the object 69 | func (d DecodeStoreCbor) Cbor() []byte { 70 | return d.cborData 71 | } 72 | -------------------------------------------------------------------------------- /cbor/encode.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cbor 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "reflect" 21 | "sync" 22 | 23 | _cbor "github.com/fxamacker/cbor/v2" 24 | "github.com/jinzhu/copier" 25 | ) 26 | 27 | func Encode(data any) ([]byte, error) { 28 | buf := bytes.NewBuffer(nil) 29 | opts := _cbor.EncOptions{ 30 | // Make sure that maps have ordered keys 31 | Sort: _cbor.SortCoreDeterministic, 32 | } 33 | em, err := opts.EncModeWithTags(customTagSet) 34 | if err != nil { 35 | return nil, err 36 | } 37 | enc := em.NewEncoder(buf) 38 | err = enc.Encode(data) 39 | return buf.Bytes(), err 40 | } 41 | 42 | var ( 43 | encodeGenericTypeCache = map[reflect.Type]reflect.Type{} 44 | encodeGenericTypeCacheMutex sync.RWMutex 45 | ) 46 | 47 | // EncodeGeneric encodes the specified object to CBOR without using the source object's 48 | // MarshalCBOR() function 49 | func EncodeGeneric(src any) ([]byte, error) { 50 | // Get source type 51 | valueSrc := reflect.ValueOf(src) 52 | typeSrc := valueSrc.Elem().Type() 53 | // Check type cache 54 | encodeGenericTypeCacheMutex.RLock() 55 | tmpTypeSrc, ok := encodeGenericTypeCache[typeSrc] 56 | encodeGenericTypeCacheMutex.RUnlock() 57 | if !ok { 58 | // Create a duplicate(-ish) struct from the destination 59 | // We do this so that we can bypass any custom MarshalCBOR() function on the 60 | // source object 61 | if valueSrc.Kind() != reflect.Pointer || 62 | valueSrc.Elem().Kind() != reflect.Struct { 63 | return nil, errors.New("source must be a pointer to a struct") 64 | } 65 | srcTypeFields := []reflect.StructField{} 66 | for i := range typeSrc.NumField() { 67 | tmpField := typeSrc.Field(i) 68 | if tmpField.IsExported() && tmpField.Name != "DecodeStoreCbor" { 69 | srcTypeFields = append(srcTypeFields, tmpField) 70 | } 71 | } 72 | tmpTypeSrc = reflect.StructOf(srcTypeFields) 73 | // Populate cache 74 | encodeGenericTypeCacheMutex.Lock() 75 | encodeGenericTypeCache[typeSrc] = tmpTypeSrc 76 | encodeGenericTypeCacheMutex.Unlock() 77 | } 78 | // Create temporary object with the type created above 79 | tmpSrc := reflect.New(tmpTypeSrc) 80 | // Copy values from source object into temporary object 81 | if err := copier.Copy(tmpSrc.Interface(), src); err != nil { 82 | return nil, err 83 | } 84 | // Encode temporary object into CBOR 85 | cborData, err := Encode(tmpSrc.Interface()) 86 | if err != nil { 87 | return nil, err 88 | } 89 | return cborData, nil 90 | } 91 | 92 | type IndefLengthList []any 93 | 94 | func (i IndefLengthList) MarshalCBOR() ([]byte, error) { 95 | ret := []byte{ 96 | // Start indefinite-length list 97 | 0x9f, 98 | } 99 | for _, item := range []any(i) { 100 | data, err := Encode(&item) 101 | if err != nil { 102 | return nil, err 103 | } 104 | ret = append(ret, data...) 105 | } 106 | ret = append( 107 | ret, 108 | // End indefinite length array 109 | byte(0xff), 110 | ) 111 | return ret, nil 112 | } 113 | 114 | type IndefLengthByteString []any 115 | 116 | func (i IndefLengthByteString) MarshalCBOR() ([]byte, error) { 117 | ret := []byte{ 118 | // Start indefinite-length bytestring 119 | 0x5f, 120 | } 121 | for _, item := range []any(i) { 122 | data, err := Encode(&item) 123 | if err != nil { 124 | return nil, err 125 | } 126 | ret = append(ret, data...) 127 | } 128 | ret = append( 129 | ret, 130 | // End indefinite length bytestring 131 | byte(0xff), 132 | ) 133 | return ret, nil 134 | } 135 | -------------------------------------------------------------------------------- /cbor/encode_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cbor_test 16 | 17 | import ( 18 | "encoding/hex" 19 | "testing" 20 | 21 | "github.com/blinklabs-io/gouroboros/cbor" 22 | ) 23 | 24 | type encodeTestDefinition struct { 25 | CborHex string 26 | Object any 27 | } 28 | 29 | var encodeTests = []encodeTestDefinition{ 30 | // Simple list of numbers 31 | { 32 | CborHex: "83010203", 33 | Object: []any{1, 2, 3}, 34 | }, 35 | } 36 | 37 | func TestEncode(t *testing.T) { 38 | for _, test := range encodeTests { 39 | cborData, err := cbor.Encode(test.Object) 40 | if err != nil { 41 | t.Fatalf("failed to encode object to CBOR: %s", err) 42 | } 43 | cborHex := hex.EncodeToString(cborData) 44 | if cborHex != test.CborHex { 45 | t.Fatalf( 46 | "object did not encode to expected CBOR\n got: %s\n wanted: %s", 47 | cborHex, 48 | test.CborHex, 49 | ) 50 | } 51 | } 52 | } 53 | 54 | func TestEncodeIndefLengthList(t *testing.T) { 55 | expectedCborHex := "9f1904d219162eff" 56 | tmpData := cbor.IndefLengthList{ 57 | 1234, 58 | 5678, 59 | } 60 | cborData, err := cbor.Encode(tmpData) 61 | if err != nil { 62 | t.Fatalf("failed to encode object to CBOR: %s", err) 63 | } 64 | cborHex := hex.EncodeToString(cborData) 65 | if cborHex != expectedCborHex { 66 | t.Fatalf( 67 | "object did not encode to expected CBOR\n got %s\n wanted: %s", 68 | cborHex, 69 | expectedCborHex, 70 | ) 71 | } 72 | } 73 | 74 | func TestEncodeIndefLengthByteString(t *testing.T) { 75 | expectedCborHex := "5f440102030443abcdefff" 76 | tmpData := cbor.IndefLengthByteString{ 77 | []byte{1, 2, 3, 4}, 78 | []byte{0xab, 0xcd, 0xef}, 79 | } 80 | cborData, err := cbor.Encode(tmpData) 81 | if err != nil { 82 | t.Fatalf("failed to encode object to CBOR: %s", err) 83 | } 84 | cborHex := hex.EncodeToString(cborData) 85 | if cborHex != expectedCborHex { 86 | t.Fatalf( 87 | "object did not encode to expected CBOR\n got %s\n wanted: %s", 88 | cborHex, 89 | expectedCborHex, 90 | ) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /cbor/tags_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cbor_test 16 | 17 | import ( 18 | "encoding/hex" 19 | "math/big" 20 | "reflect" 21 | "testing" 22 | 23 | "github.com/blinklabs-io/gouroboros/cbor" 24 | ) 25 | 26 | var tagsTestDefs = []struct { 27 | cborHex string 28 | object any 29 | }{ 30 | { 31 | cborHex: "d81843abcdef", 32 | object: cbor.WrappedCbor([]byte{0xab, 0xcd, 0xef}), 33 | }, 34 | { 35 | cborHex: "d81e82031903e8", 36 | object: cbor.Rat{ 37 | Rat: big.NewRat(3, 1000), 38 | }, 39 | }, 40 | { 41 | cborHex: "d9010283010203", 42 | object: cbor.Set( 43 | []any{ 44 | uint64(1), uint64(2), uint64(3), 45 | }, 46 | ), 47 | }, 48 | { 49 | cborHex: "d90103a201020304", 50 | object: cbor.Map( 51 | map[any]any{ 52 | uint64(1): uint64(2), 53 | uint64(3): uint64(4), 54 | }, 55 | ), 56 | }, 57 | // 30([9223372036854775809, 10000000000000000000]) 58 | { 59 | cborHex: "d81e821b80000000000000011b8ac7230489e80000", 60 | object: cbor.Rat{ 61 | Rat: new(big.Rat).SetFrac( 62 | new(big.Int).SetUint64(9223372036854775809), 63 | new(big.Int).SetUint64(10000000000000000000), 64 | ), 65 | }, 66 | }, 67 | // 30([-1, 2]) 68 | { 69 | cborHex: "d81e822002", 70 | object: cbor.Rat{ 71 | Rat: big.NewRat(-1, 2), 72 | }, 73 | }, 74 | } 75 | 76 | func TestTagsDecode(t *testing.T) { 77 | for _, testDef := range tagsTestDefs { 78 | cborData, err := hex.DecodeString(testDef.cborHex) 79 | if err != nil { 80 | t.Fatalf("failed to decode CBOR hex: %s", err) 81 | } 82 | var dest any 83 | if _, err := cbor.Decode(cborData, &dest); err != nil { 84 | t.Fatalf("failed to decode CBOR: %s", err) 85 | } 86 | if !reflect.DeepEqual(dest, testDef.object) { 87 | t.Fatalf( 88 | "CBOR did not decode to expected object\n got: %#v\n wanted: %#v", 89 | dest, 90 | testDef.object, 91 | ) 92 | } 93 | } 94 | } 95 | 96 | func TestTagsEncode(t *testing.T) { 97 | for _, testDef := range tagsTestDefs { 98 | cborData, err := cbor.Encode(testDef.object) 99 | if err != nil { 100 | t.Fatalf("failed to encode object to CBOR: %s", err) 101 | } 102 | cborHex := hex.EncodeToString(cborData) 103 | if cborHex != testDef.cborHex { 104 | t.Fatalf( 105 | "object did not encode to expected CBOR\n got: %s\n wanted: %s", 106 | cborHex, 107 | testDef.cborHex, 108 | ) 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /cmd/common/cmdline.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package common 16 | 17 | import ( 18 | "flag" 19 | "fmt" 20 | "os" 21 | 22 | ouroboros "github.com/blinklabs-io/gouroboros" 23 | ) 24 | 25 | type GlobalFlags struct { 26 | Flagset *flag.FlagSet 27 | Socket string 28 | Address string 29 | UseTls bool 30 | NtnProto bool 31 | Network string 32 | NetworkMagic int 33 | } 34 | 35 | func NewGlobalFlags() *GlobalFlags { 36 | var name string 37 | if os.Args == nil { 38 | name = "gouroboros" 39 | } else { 40 | name = os.Args[0] 41 | } 42 | f := &GlobalFlags{ 43 | Flagset: flag.NewFlagSet(name, flag.ExitOnError), 44 | } 45 | f.Flagset.StringVar( 46 | &f.Socket, 47 | "socket", 48 | "", 49 | "UNIX socket path to connect to", 50 | ) 51 | f.Flagset.StringVar( 52 | &f.Address, 53 | "address", 54 | "", 55 | "TCP address to connect to in address:port format", 56 | ) 57 | f.Flagset.BoolVar(&f.UseTls, "tls", false, "enable TLS") 58 | f.Flagset.BoolVar( 59 | &f.NtnProto, 60 | "ntn", 61 | false, 62 | "use node-to-node protocol (defaults to node-to-client)", 63 | ) 64 | f.Flagset.StringVar( 65 | &f.Network, 66 | "network", 67 | "preview", 68 | "specifies network that node is participating in", 69 | ) 70 | f.Flagset.IntVar( 71 | &f.NetworkMagic, 72 | "network-magic", 73 | 0, 74 | "specifies network magic value. this overrides the -network option", 75 | ) 76 | return f 77 | } 78 | 79 | func (f *GlobalFlags) Parse() { 80 | if os.Args == nil { 81 | fmt.Printf("failed to parse command line\n") 82 | os.Exit(1) 83 | } 84 | if err := f.Flagset.Parse(os.Args[1:]); err != nil { 85 | fmt.Printf("failed to parse command args: %s\n", err) 86 | os.Exit(1) 87 | } 88 | if f.NetworkMagic == 0 { 89 | network, ok := ouroboros.NetworkByName(f.Network) 90 | if !ok { 91 | fmt.Printf("Unknown network specified: %s\n", f.Network) 92 | os.Exit(1) 93 | } 94 | f.NetworkMagic = int(network.NetworkMagic) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /cmd/common/connect.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package common 16 | 17 | import ( 18 | "crypto/tls" 19 | "fmt" 20 | "net" 21 | "os" 22 | ) 23 | 24 | func CreateClientConnection(f *GlobalFlags) net.Conn { 25 | var err error 26 | var conn net.Conn 27 | var dialProto string 28 | var dialAddress string 29 | if f.Socket != "" { 30 | dialProto = "unix" 31 | dialAddress = f.Socket 32 | } else if f.Address != "" { 33 | dialProto = "tcp" 34 | dialAddress = f.Address 35 | } else { 36 | fmt.Printf("You must specify one of -socket or -address\n\n") 37 | f.Flagset.PrintDefaults() 38 | os.Exit(1) 39 | } 40 | if f.UseTls { 41 | conn, err = tls.Dial(dialProto, dialAddress, nil) 42 | } else { 43 | conn, err = net.Dial(dialProto, dialAddress) 44 | } 45 | if err != nil { 46 | fmt.Printf("Connection failed: %s\n", err) 47 | os.Exit(1) 48 | } 49 | return conn 50 | } 51 | -------------------------------------------------------------------------------- /cmd/gouroboros/localtxsubmission.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "encoding/hex" 19 | "encoding/json" 20 | "flag" 21 | "fmt" 22 | "os" 23 | 24 | ouroboros "github.com/blinklabs-io/gouroboros" 25 | "github.com/blinklabs-io/gouroboros/ledger" 26 | "github.com/blinklabs-io/gouroboros/protocol/localtxsubmission" 27 | ) 28 | 29 | type localTxSubmissionFlags struct { 30 | flagset *flag.FlagSet 31 | txFile string 32 | rawTxFile string 33 | } 34 | 35 | func newLocalTxSubmissionFlags() *localTxSubmissionFlags { 36 | f := &localTxSubmissionFlags{ 37 | flagset: flag.NewFlagSet("local-tx-submission", flag.ExitOnError), 38 | } 39 | f.flagset.StringVar( 40 | &f.txFile, 41 | "tx-file", 42 | "", 43 | "path to the JSON transaction file to submit", 44 | ) 45 | f.flagset.StringVar( 46 | &f.rawTxFile, 47 | "raw-tx-file", 48 | "", 49 | "path to the raw transaction file to submit", 50 | ) 51 | return f 52 | } 53 | 54 | func buildLocalTxSubmissionConfig() localtxsubmission.Config { 55 | return localtxsubmission.NewConfig() 56 | } 57 | 58 | func testLocalTxSubmission(f *globalFlags) { 59 | localTxSubmissionFlags := newLocalTxSubmissionFlags() 60 | err := localTxSubmissionFlags.flagset.Parse(f.flagset.Args()[1:]) 61 | if err != nil { 62 | fmt.Printf("failed to parse subcommand args: %s\n", err) 63 | os.Exit(1) 64 | } 65 | if localTxSubmissionFlags.txFile == "" && 66 | localTxSubmissionFlags.rawTxFile == "" { 67 | fmt.Printf("you must specify -tx-file or -raw-tx-file\n") 68 | os.Exit(1) 69 | } 70 | 71 | conn := createClientConnection(f) 72 | errorChan := make(chan error) 73 | go func() { 74 | for { 75 | err := <-errorChan 76 | fmt.Printf("ERROR(async): %s\n", err) 77 | os.Exit(1) 78 | } 79 | }() 80 | o, err := ouroboros.New( 81 | ouroboros.WithConnection(conn), 82 | ouroboros.WithNetworkMagic(uint32(f.networkMagic)), // #nosec G115 83 | ouroboros.WithErrorChan(errorChan), 84 | ouroboros.WithNodeToNode(f.ntnProto), 85 | ouroboros.WithKeepAlive(true), 86 | ouroboros.WithLocalTxSubmissionConfig(buildLocalTxSubmissionConfig()), 87 | ) 88 | if err != nil { 89 | fmt.Printf("ERROR: %s\n", err) 90 | os.Exit(1) 91 | } 92 | 93 | var txBytes []byte 94 | if localTxSubmissionFlags.txFile != "" { 95 | txData, err := os.ReadFile(localTxSubmissionFlags.txFile) 96 | if err != nil { 97 | fmt.Printf("Failed to load transaction file: %s\n", err) 98 | os.Exit(1) 99 | } 100 | 101 | var jsonData map[string]string 102 | err = json.Unmarshal(txData, &jsonData) 103 | if err != nil { 104 | fmt.Printf("failed to parse transaction file: %s\n", err) 105 | os.Exit(1) 106 | } 107 | 108 | txBytes, err = hex.DecodeString(jsonData["cborHex"]) 109 | if err != nil { 110 | fmt.Printf("failed to decode transaction: %s\n", err) 111 | os.Exit(1) 112 | } 113 | } else { 114 | txBytes, err = os.ReadFile(localTxSubmissionFlags.rawTxFile) 115 | if err != nil { 116 | fmt.Printf("Failed to load transaction file: %s\n", err) 117 | os.Exit(1) 118 | } 119 | } 120 | 121 | if err = o.LocalTxSubmission().Client.SubmitTx(ledger.TxTypeAlonzo, txBytes); err != nil { 122 | fmt.Printf("Error submitting transaction: %s\n", err) 123 | os.Exit(1) 124 | } 125 | fmt.Print("The transaction was accepted\n") 126 | } 127 | -------------------------------------------------------------------------------- /cmd/gouroboros/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "crypto/tls" 19 | "flag" 20 | "fmt" 21 | "net" 22 | "os" 23 | 24 | ouroboros "github.com/blinklabs-io/gouroboros" 25 | ) 26 | 27 | type globalFlags struct { 28 | flagset *flag.FlagSet 29 | socket string 30 | address string 31 | useTls bool 32 | ntnProto bool 33 | network string 34 | networkMagic int 35 | } 36 | 37 | func newGlobalFlags() *globalFlags { 38 | var name string 39 | if os.Args == nil { 40 | name = "gouroboros" 41 | } else { 42 | name = os.Args[0] 43 | } 44 | f := &globalFlags{ 45 | flagset: flag.NewFlagSet(name, flag.ExitOnError), 46 | } 47 | f.flagset.StringVar( 48 | &f.socket, 49 | "socket", 50 | "", 51 | "UNIX socket path to connect to", 52 | ) 53 | f.flagset.StringVar( 54 | &f.address, 55 | "address", 56 | "", 57 | "TCP address to connect to in address:port format", 58 | ) 59 | f.flagset.BoolVar(&f.useTls, "tls", false, "enable TLS") 60 | f.flagset.BoolVar( 61 | &f.ntnProto, 62 | "ntn", 63 | false, 64 | "use node-to-node protocol (defaults to node-to-client)", 65 | ) 66 | f.flagset.StringVar( 67 | &f.network, 68 | "network", 69 | "preview", 70 | "specifies network that node is participating in", 71 | ) 72 | f.flagset.IntVar( 73 | &f.networkMagic, 74 | "network-magic", 75 | 0, 76 | "specifies network magic value. this overrides the -network option", 77 | ) 78 | return f 79 | } 80 | 81 | func main() { 82 | if os.Args == nil { 83 | fmt.Printf("failed parsing command line\n") 84 | os.Exit(1) 85 | } 86 | f := newGlobalFlags() 87 | err := f.flagset.Parse(os.Args[1:]) 88 | if err != nil { 89 | fmt.Printf("failed to parse command args: %s\n", err) 90 | os.Exit(1) 91 | } 92 | 93 | if f.networkMagic == 0 { 94 | network, ok := ouroboros.NetworkByName(f.network) 95 | if !ok { 96 | fmt.Printf("Unknown network specified: %s\n", f.network) 97 | os.Exit(1) 98 | } 99 | f.networkMagic = int(network.NetworkMagic) 100 | } 101 | 102 | subcommands := map[string]func(*globalFlags){ 103 | "chain-sync": testChainSync, 104 | "local-tx-submission": testLocalTxSubmission, 105 | "server": testServer, 106 | "query": testQuery, 107 | "mem-usage": testMemUsage, 108 | } 109 | 110 | if len(f.flagset.Args()) == 0 { 111 | fmt.Printf("\n") 112 | fmt.Printf("Usage of gouroboros:\n") 113 | fmt.Printf("\n") 114 | fmt.Printf("Commands:\n") 115 | 116 | for k := range subcommands { 117 | fmt.Printf(" %s\n", k) 118 | } 119 | 120 | fmt.Printf("\n") 121 | fmt.Printf("Global flags:\n") 122 | f.flagset.PrintDefaults() 123 | 124 | os.Exit(1) 125 | } 126 | 127 | fn, ok := subcommands[f.flagset.Arg(0)] 128 | if !ok { 129 | fmt.Printf("Unknown subcommand: %s\n", f.flagset.Arg(0)) 130 | os.Exit(1) 131 | } 132 | 133 | fn(f) 134 | } 135 | 136 | func createClientConnection(f *globalFlags) net.Conn { 137 | var err error 138 | var conn net.Conn 139 | var dialProto string 140 | var dialAddress string 141 | if f.socket != "" { 142 | dialProto = "unix" 143 | dialAddress = f.socket 144 | } else if f.address != "" { 145 | dialProto = "tcp" 146 | dialAddress = f.address 147 | } else { 148 | fmt.Printf("You must specify one of -socket or -address\n\n") 149 | flag.PrintDefaults() 150 | os.Exit(1) 151 | } 152 | if f.useTls { 153 | conn, err = tls.Dial(dialProto, dialAddress, nil) 154 | } else { 155 | conn, err = net.Dial(dialProto, dialAddress) 156 | } 157 | if err != nil { 158 | fmt.Printf("Connection failed: %s\n", err) 159 | os.Exit(1) 160 | } 161 | return conn 162 | } 163 | -------------------------------------------------------------------------------- /cmd/gouroboros/mem_usage.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "flag" 19 | "fmt" 20 | "log" 21 | "net/http" 22 | _ "net/http/pprof" // #nosec G108 23 | "os" 24 | "runtime" 25 | "runtime/pprof" 26 | "time" 27 | 28 | ouroboros "github.com/blinklabs-io/gouroboros" 29 | ) 30 | 31 | type memUsageFlags struct { 32 | flagset *flag.FlagSet 33 | startEra string 34 | tip bool 35 | debugPort int 36 | } 37 | 38 | func newMemUsageFlags() *memUsageFlags { 39 | f := &memUsageFlags{ 40 | flagset: flag.NewFlagSet("mem-usage", flag.ExitOnError), 41 | } 42 | f.flagset.StringVar( 43 | &f.startEra, 44 | "start-era", 45 | "genesis", 46 | "era which to start chain-sync at", 47 | ) 48 | f.flagset.BoolVar( 49 | &f.tip, 50 | "tip", 51 | false, 52 | "start chain-sync at current chain tip", 53 | ) 54 | f.flagset.IntVar(&f.debugPort, "debug-port", 8080, "pprof port") 55 | return f 56 | } 57 | 58 | func testMemUsage(f *globalFlags) { 59 | memUsageFlags := newMemUsageFlags() 60 | err := memUsageFlags.flagset.Parse(f.flagset.Args()[1:]) 61 | if err != nil { 62 | fmt.Printf("failed to parse subcommand args: %s\n", err) 63 | os.Exit(1) 64 | } 65 | 66 | // Start pprof listener 67 | log.Printf( 68 | "Starting pprof listener on http://0.0.0.0:%d/debug/pprof\n", 69 | memUsageFlags.debugPort, 70 | ) 71 | go func() { 72 | log.Println( 73 | // This is a test program, no timeouts 74 | // #nosec G114 75 | http.ListenAndServe( 76 | fmt.Sprintf(":%d", memUsageFlags.debugPort), 77 | nil, 78 | ), 79 | ) 80 | }() 81 | 82 | for range 10 { 83 | showMemoryStats("open") 84 | 85 | conn := createClientConnection(f) 86 | errorChan := make(chan error) 87 | go func() { 88 | for { 89 | err, ok := <-errorChan 90 | if !ok { 91 | return 92 | } 93 | fmt.Printf("ERROR: %s\n", err) 94 | os.Exit(1) 95 | } 96 | }() 97 | o, err := ouroboros.New( 98 | ouroboros.WithConnection(conn), 99 | ouroboros.WithNetworkMagic(uint32(f.networkMagic)), // #nosec G115 100 | ouroboros.WithErrorChan(errorChan), 101 | ouroboros.WithNodeToNode(f.ntnProto), 102 | ouroboros.WithKeepAlive(true), 103 | ) 104 | if err != nil { 105 | fmt.Printf("ERROR: %s\n", err) 106 | os.Exit(1) 107 | } 108 | 109 | tip, err := o.ChainSync().Client.GetCurrentTip() 110 | if err != nil { 111 | fmt.Printf("ERROR: %s\n", err) 112 | os.Exit(1) 113 | } 114 | 115 | log.Printf( 116 | "tip: slot = %d, hash = %x\n", 117 | tip.Point.Slot, 118 | tip.Point.Hash, 119 | ) 120 | 121 | if err := o.Close(); err != nil { 122 | fmt.Printf("ERROR: %s\n", err) 123 | } 124 | 125 | showMemoryStats("close") 126 | 127 | time.Sleep(5 * time.Second) 128 | 129 | runtime.GC() 130 | 131 | showMemoryStats("after GC") 132 | } 133 | 134 | if err := pprof.Lookup("goroutine").WriteTo(os.Stdout, 1); err != nil { 135 | fmt.Printf("ERROR: %s\n", err) 136 | os.Exit(1) 137 | } 138 | 139 | fmt.Printf("waiting forever") 140 | select {} 141 | } 142 | 143 | func showMemoryStats(tag string) { 144 | var m runtime.MemStats 145 | runtime.ReadMemStats(&m) 146 | log.Printf("[%s] HeapAlloc: %dKiB\n", tag, m.HeapAlloc/1024) 147 | } 148 | -------------------------------------------------------------------------------- /cmd/gouroboros/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "errors" 19 | "flag" 20 | "fmt" 21 | "net" 22 | "os" 23 | 24 | ouroboros "github.com/blinklabs-io/gouroboros" 25 | ) 26 | 27 | type serverFlags struct { 28 | flagset *flag.FlagSet 29 | // txFile string 30 | } 31 | 32 | func newServerFlags() *serverFlags { 33 | f := &serverFlags{ 34 | flagset: flag.NewFlagSet("server", flag.ExitOnError), 35 | } 36 | // f.flagset.StringVar(&f.txFile, "tx-file", "", "path to the transaction file to submit") 37 | return f 38 | } 39 | 40 | func createListenerSocket(f *globalFlags) (net.Listener, error) { 41 | var err error 42 | var listen net.Listener 43 | 44 | switch { 45 | case f.socket != "": 46 | if err := os.Remove(f.socket); err != nil { 47 | return nil, fmt.Errorf("failed to remove existing socket: %w", err) 48 | } 49 | listen, err = net.Listen("unix", f.socket) 50 | if err != nil { 51 | return nil, fmt.Errorf("failed to open listening socket: %w", err) 52 | } 53 | case f.address != "": 54 | listen, err = net.Listen("tcp", f.address) 55 | if err != nil { 56 | return nil, fmt.Errorf("failed to open listening socket: %w", err) 57 | } 58 | default: 59 | return nil, errors.New("no listening address or socket specified") 60 | } 61 | 62 | return listen, nil 63 | } 64 | 65 | func testServer(f *globalFlags) { 66 | serverFlags := newServerFlags() 67 | err := serverFlags.flagset.Parse(f.flagset.Args()[1:]) 68 | if err != nil { 69 | fmt.Printf("failed to parse subcommand args: %s\n", err) 70 | os.Exit(1) 71 | } 72 | 73 | listen, err := createListenerSocket(f) 74 | if err != nil { 75 | fmt.Printf("ERROR: failed to create listener: %s\n", err) 76 | os.Exit(1) 77 | } 78 | 79 | for { 80 | conn, err := listen.Accept() 81 | if err != nil { 82 | fmt.Printf("ERROR: failed to accept connection: %s\n", err) 83 | continue 84 | } 85 | errorChan := make(chan error) 86 | go func() { 87 | for { 88 | err := <-errorChan 89 | fmt.Printf("ERROR: %s\n", err) 90 | } 91 | }() 92 | _, err = ouroboros.New( 93 | ouroboros.WithConnection(conn), 94 | ouroboros.WithNetworkMagic(uint32(f.networkMagic)), // #nosec G115 95 | ouroboros.WithErrorChan(errorChan), 96 | ouroboros.WithNodeToNode(f.ntnProto), 97 | ouroboros.WithServer(true), 98 | ) 99 | if err != nil { 100 | fmt.Printf("ERROR: %s\n", err) 101 | } 102 | fmt.Printf("handshake completed...disconnecting\n") 103 | conn.Close() 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /connection/id.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package connection 16 | 17 | import ( 18 | "fmt" 19 | "net" 20 | ) 21 | 22 | type ConnectionId struct { 23 | LocalAddr net.Addr 24 | RemoteAddr net.Addr 25 | } 26 | 27 | func (c ConnectionId) String() string { 28 | return fmt.Sprintf( 29 | "%s<->%s", 30 | c.LocalAddr.String(), 31 | c.RemoteAddr.String(), 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /connection_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ouroboros_test 16 | 17 | import ( 18 | "fmt" 19 | "testing" 20 | "time" 21 | 22 | ouroboros "github.com/blinklabs-io/gouroboros" 23 | "github.com/blinklabs-io/ouroboros-mock" 24 | "go.uber.org/goleak" 25 | ) 26 | 27 | // Ensure that we don't panic when closing the Connection object after a failed Dial() call 28 | func TestDialFailClose(t *testing.T) { 29 | defer goleak.VerifyNone(t) 30 | oConn, err := ouroboros.New() 31 | if err != nil { 32 | t.Fatalf("unexpected error when creating Connection object: %s", err) 33 | } 34 | err = oConn.Dial("unix", "/path/does/not/exist") 35 | if err == nil { 36 | t.Fatalf("did not get expected failure on Dial()") 37 | } 38 | // Close connection 39 | oConn.Close() 40 | } 41 | 42 | func TestDoubleClose(t *testing.T) { 43 | defer goleak.VerifyNone(t) 44 | mockConn := ouroboros_mock.NewConnection( 45 | ouroboros_mock.ProtocolRoleClient, 46 | []ouroboros_mock.ConversationEntry{ 47 | ouroboros_mock.ConversationEntryHandshakeRequestGeneric, 48 | ouroboros_mock.ConversationEntryHandshakeNtCResponse, 49 | }, 50 | ) 51 | oConn, err := ouroboros.New( 52 | ouroboros.WithConnection(mockConn), 53 | ouroboros.WithNetworkMagic(ouroboros_mock.MockNetworkMagic), 54 | ) 55 | if err != nil { 56 | t.Fatalf("unexpected error when creating Connection object: %s", err) 57 | } 58 | // Async error handler 59 | go func() { 60 | err, ok := <-oConn.ErrorChan() 61 | if !ok { 62 | return 63 | } 64 | // We can't call t.Fatalf() from a different Goroutine, so we panic instead 65 | panic(fmt.Sprintf("unexpected Ouroboros connection error: %s", err)) 66 | }() 67 | // Close connection 68 | if err := oConn.Close(); err != nil { 69 | t.Fatalf("unexpected error when closing Connection object: %s", err) 70 | } 71 | // Close connection again 72 | if err := oConn.Close(); err != nil { 73 | t.Fatalf( 74 | "unexpected error when closing Connection object again: %s", 75 | err, 76 | ) 77 | } 78 | // Wait for connection shutdown 79 | select { 80 | case <-oConn.ErrorChan(): 81 | case <-time.After(10 * time.Second): 82 | t.Errorf("did not shutdown within timeout") 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/blinklabs-io/gouroboros 2 | 3 | go 1.23.6 4 | 5 | toolchain go1.24.1 6 | 7 | require ( 8 | filippo.io/edwards25519 v1.1.0 9 | github.com/blinklabs-io/ouroboros-mock v0.3.8 10 | github.com/btcsuite/btcd/btcutil v1.1.6 11 | github.com/fxamacker/cbor/v2 v2.8.0 12 | github.com/jinzhu/copier v0.4.0 13 | github.com/stretchr/testify v1.10.0 14 | github.com/utxorpc/go-codegen v0.16.0 15 | go.uber.org/goleak v1.3.0 16 | golang.org/x/crypto v0.38.0 17 | ) 18 | 19 | require ( 20 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 21 | github.com/google/go-cmp v0.6.0 // indirect 22 | github.com/kr/text v0.2.0 // indirect 23 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 24 | github.com/x448/float16 v0.8.4 // indirect 25 | golang.org/x/sys v0.33.0 // indirect 26 | google.golang.org/protobuf v1.36.3 // indirect 27 | gopkg.in/yaml.v3 v3.0.1 // indirect 28 | ) 29 | -------------------------------------------------------------------------------- /internal/test/helpers.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "encoding/json" 7 | "fmt" 8 | "reflect" 9 | "strings" 10 | ) 11 | 12 | // DecodeHexString is a helper function for tests that decodes hex strings. It doesn't return 13 | // an error value, which makes it usable inline. 14 | func DecodeHexString(hexData string) []byte { 15 | // Strip off any leading/trailing whitespace in hex string 16 | hexData = strings.TrimSpace(hexData) 17 | decoded, err := hex.DecodeString(hexData) 18 | if err != nil { 19 | panic(fmt.Sprintf("error decoding hex: %s", err)) 20 | } 21 | return decoded 22 | } 23 | 24 | // JsonStringsEqual is a helper function for tests that compares JSON strings. To account for 25 | // differences in whitespace, map key ordering, etc., we unmarshal the JSON strings into 26 | // objects and then compare the objects 27 | func JsonStringsEqual(jsonData1 []byte, jsonData2 []byte) bool { 28 | // Short-circuit for the happy path where they match exactly 29 | if bytes.Equal(jsonData1, jsonData2) { 30 | return true 31 | } 32 | // Decode provided JSON strings 33 | var tmpObj1 any 34 | if err := json.Unmarshal(jsonData1, &tmpObj1); err != nil { 35 | return false 36 | } 37 | var tmpObj2 any 38 | if err := json.Unmarshal(jsonData2, &tmpObj2); err != nil { 39 | return false 40 | } 41 | return reflect.DeepEqual(tmpObj1, tmpObj2) 42 | } 43 | -------------------------------------------------------------------------------- /internal/test/ledger/ledger.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package test_ledger 16 | 17 | import ( 18 | "errors" 19 | 20 | "github.com/blinklabs-io/gouroboros/ledger/common" 21 | ) 22 | 23 | type MockLedgerState struct { 24 | MockNetworkId uint 25 | MockUtxos []common.Utxo 26 | MockStakeRegistration []common.StakeRegistrationCertificate 27 | MockPoolRegistration []common.PoolRegistrationCertificate 28 | } 29 | 30 | func (ls MockLedgerState) NetworkId() uint { 31 | return ls.MockNetworkId 32 | } 33 | 34 | func (ls MockLedgerState) UtxoById( 35 | id common.TransactionInput, 36 | ) (common.Utxo, error) { 37 | for _, tmpUtxo := range ls.MockUtxos { 38 | if id.Index() != tmpUtxo.Id.Index() { 39 | continue 40 | } 41 | if string(id.Id().Bytes()) != string(tmpUtxo.Id.Id().Bytes()) { 42 | continue 43 | } 44 | return tmpUtxo, nil 45 | } 46 | return common.Utxo{}, errors.New("not found") 47 | } 48 | 49 | func (ls MockLedgerState) StakeRegistration( 50 | stakingKey []byte, 51 | ) ([]common.StakeRegistrationCertificate, error) { 52 | ret := []common.StakeRegistrationCertificate{} 53 | for _, cert := range ls.MockStakeRegistration { 54 | if string( 55 | common.Blake2b224(cert.StakeRegistration.Credential).Bytes(), 56 | ) == string( 57 | stakingKey, 58 | ) { 59 | ret = append(ret, cert) 60 | } 61 | } 62 | return ret, nil 63 | } 64 | 65 | func (ls MockLedgerState) PoolRegistration( 66 | poolKeyHash []byte, 67 | ) ([]common.PoolRegistrationCertificate, error) { 68 | ret := []common.PoolRegistrationCertificate{} 69 | for _, cert := range ls.MockPoolRegistration { 70 | if string( 71 | common.Blake2b224(cert.Operator).Bytes(), 72 | ) == string( 73 | poolKeyHash, 74 | ) { 75 | ret = append(ret, cert) 76 | } 77 | } 78 | return ret, nil 79 | } 80 | -------------------------------------------------------------------------------- /ledger/allegra.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ledger 16 | 17 | import "github.com/blinklabs-io/gouroboros/ledger/allegra" 18 | 19 | // The below are compatibility types, constants, and functions for the Allegra era 20 | // to keep existing code working after a refactor of the ledger package 21 | 22 | // Allegra types 23 | type ( 24 | AllegraBlock = allegra.AllegraBlock 25 | AllegraTransaction = allegra.AllegraTransaction 26 | AllegraTransactionBody = allegra.AllegraTransactionBody 27 | AllegraProtocolParameters = allegra.AllegraProtocolParameters 28 | AllegraProtocolParameterUpdate = allegra.AllegraProtocolParameterUpdate 29 | ) 30 | 31 | // Allegra constants 32 | const ( 33 | EraIdAllegra = allegra.EraIdAllegra 34 | BlockTypeAllegra = allegra.BlockTypeAllegra 35 | BlockHeaderTypeAllegra = allegra.BlockHeaderTypeAllegra 36 | TxTypeAllegra = allegra.TxTypeAllegra 37 | ) 38 | 39 | // Allegra functions 40 | var ( 41 | NewAllegraBlockFromCbor = allegra.NewAllegraBlockFromCbor 42 | NewAllegraBlockHeaderFromCbor = allegra.NewAllegraBlockHeaderFromCbor 43 | NewAllegraTransactionFromCbor = allegra.NewAllegraTransactionFromCbor 44 | NewAllegraTransactionBodyFromCbor = allegra.NewAllegraTransactionBodyFromCbor 45 | ) 46 | -------------------------------------------------------------------------------- /ledger/allegra/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package allegra 16 | 17 | import ( 18 | "fmt" 19 | ) 20 | 21 | type OutsideValidityIntervalUtxoError struct { 22 | ValidityIntervalStart uint64 23 | Slot uint64 24 | } 25 | 26 | func (e OutsideValidityIntervalUtxoError) Error() string { 27 | return fmt.Sprintf( 28 | "outside validity interval: start %d, slot %d", 29 | e.ValidityIntervalStart, 30 | e.Slot, 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /ledger/allegra/pparams.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package allegra 16 | 17 | import ( 18 | "github.com/blinklabs-io/gouroboros/ledger/shelley" 19 | ) 20 | 21 | type AllegraProtocolParameters = shelley.ShelleyProtocolParameters 22 | 23 | type AllegraProtocolParameterUpdate = shelley.ShelleyProtocolParameterUpdate 24 | 25 | func UpgradePParams( 26 | prevPParams shelley.ShelleyProtocolParameters, 27 | ) AllegraProtocolParameters { 28 | return AllegraProtocolParameters(prevPParams) 29 | } 30 | -------------------------------------------------------------------------------- /ledger/allegra/pparams_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package allegra_test 16 | 17 | import ( 18 | "encoding/hex" 19 | "math/big" 20 | "reflect" 21 | "testing" 22 | 23 | "github.com/blinklabs-io/gouroboros/cbor" 24 | "github.com/blinklabs-io/gouroboros/ledger/allegra" 25 | "github.com/utxorpc/go-codegen/utxorpc/v1alpha/cardano" 26 | ) 27 | 28 | func TestAllegraProtocolParamsUpdate(t *testing.T) { 29 | testDefs := []struct { 30 | startParams allegra.AllegraProtocolParameters 31 | updateCbor string 32 | expectedParams allegra.AllegraProtocolParameters 33 | }{ 34 | { 35 | startParams: allegra.AllegraProtocolParameters{ 36 | Decentralization: &cbor.Rat{Rat: new(big.Rat).SetInt64(1)}, 37 | }, 38 | updateCbor: "a10cd81e82090a", 39 | expectedParams: allegra.AllegraProtocolParameters{ 40 | Decentralization: &cbor.Rat{Rat: big.NewRat(9, 10)}, 41 | }, 42 | }, 43 | { 44 | startParams: allegra.AllegraProtocolParameters{ 45 | ProtocolMajor: 3, 46 | }, 47 | updateCbor: "a10e820400", 48 | expectedParams: allegra.AllegraProtocolParameters{ 49 | ProtocolMajor: 4, 50 | }, 51 | }, 52 | } 53 | for _, testDef := range testDefs { 54 | cborBytes, err := hex.DecodeString(testDef.updateCbor) 55 | if err != nil { 56 | t.Fatalf("unexpected error: %s", err) 57 | } 58 | var tmpUpdate allegra.AllegraProtocolParameterUpdate 59 | if _, err := cbor.Decode(cborBytes, &tmpUpdate); err != nil { 60 | t.Fatalf("unexpected error: %s", err) 61 | } 62 | tmpParams := testDef.startParams 63 | tmpParams.Update(&tmpUpdate) 64 | if !reflect.DeepEqual(tmpParams, testDef.expectedParams) { 65 | t.Fatalf( 66 | "did not get expected params:\n got: %#v\n wanted: %#v", 67 | tmpParams, 68 | testDef.expectedParams, 69 | ) 70 | } 71 | } 72 | } 73 | 74 | func TestAllegraUtxorpc(t *testing.T) { 75 | inputParams := allegra.AllegraProtocolParameters{ 76 | MinFeeA: 500, 77 | MinFeeB: 2, 78 | MaxBlockBodySize: 65536, 79 | MaxTxSize: 16384, 80 | MaxBlockHeaderSize: 1024, 81 | KeyDeposit: 2000, 82 | PoolDeposit: 500000, 83 | MaxEpoch: 2160, 84 | NOpt: 100, 85 | A0: &cbor.Rat{Rat: big.NewRat(1, 2)}, 86 | Rho: &cbor.Rat{Rat: big.NewRat(3, 4)}, 87 | Tau: &cbor.Rat{Rat: big.NewRat(5, 6)}, 88 | ProtocolMajor: 8, 89 | ProtocolMinor: 0, 90 | MinUtxoValue: 1000000, 91 | } 92 | 93 | expectedUtxorpc := &cardano.PParams{ 94 | MinFeeCoefficient: 500, 95 | MinFeeConstant: 2, 96 | MaxBlockBodySize: 65536, 97 | MaxTxSize: 16384, 98 | MaxBlockHeaderSize: 1024, 99 | StakeKeyDeposit: 2000, 100 | PoolDeposit: 500000, 101 | PoolRetirementEpochBound: 2160, 102 | DesiredNumberOfPools: 100, 103 | PoolInfluence: &cardano.RationalNumber{ 104 | Numerator: int32(1), 105 | Denominator: uint32(2), 106 | }, 107 | MonetaryExpansion: &cardano.RationalNumber{ 108 | Numerator: int32(3), 109 | Denominator: uint32(4), 110 | }, 111 | TreasuryExpansion: &cardano.RationalNumber{ 112 | Numerator: int32(5), 113 | Denominator: uint32(6), 114 | }, 115 | ProtocolVersion: &cardano.ProtocolVersion{ 116 | Major: 8, 117 | Minor: 0, 118 | }, 119 | } 120 | 121 | result := inputParams.Utxorpc() 122 | 123 | if !reflect.DeepEqual(result, expectedUtxorpc) { 124 | t.Fatalf( 125 | "Utxorpc() test failed for Allegra:\nExpected: %#v\nGot: %#v", 126 | expectedUtxorpc, 127 | result, 128 | ) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /ledger/alonzo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ledger 16 | 17 | import "github.com/blinklabs-io/gouroboros/ledger/alonzo" 18 | 19 | // The below are compatibility types, constants, and functions for the Alonzo era 20 | // to keep existing code working after a refactor of the ledger package 21 | 22 | // Alonzo types 23 | type ( 24 | AlonzoBlock = alonzo.AlonzoBlock 25 | AlonzoBlockHeader = alonzo.AlonzoBlockHeader 26 | AlonzoTransaction = alonzo.AlonzoTransaction 27 | AlonzoTransactionBody = alonzo.AlonzoTransactionBody 28 | AlonzoTransactionOutput = alonzo.AlonzoTransactionOutput 29 | AlonzoTransactionWitnessSet = alonzo.AlonzoTransactionWitnessSet 30 | AlonzoProtocolParameters = alonzo.AlonzoProtocolParameters 31 | AlonzoProtocolParameterUpdate = alonzo.AlonzoProtocolParameterUpdate 32 | ) 33 | 34 | // Alonzo constants 35 | const ( 36 | EraIdAlonzo = alonzo.EraIdAlonzo 37 | BlockTypeAlonzo = alonzo.BlockTypeAlonzo 38 | BlockHeaderTypeAlonzo = alonzo.BlockHeaderTypeAlonzo 39 | TxTypeAlonzo = alonzo.TxTypeAlonzo 40 | ) 41 | 42 | // Alonzo functions 43 | var ( 44 | NewAlonzoBlockFromCbor = alonzo.NewAlonzoBlockFromCbor 45 | NewAlonzoBlockHeaderFromCbor = alonzo.NewAlonzoBlockHeaderFromCbor 46 | NewAlonzoTransactionFromCbor = alonzo.NewAlonzoTransactionFromCbor 47 | NewAlonzoTransactionBodyFromCbor = alonzo.NewAlonzoTransactionBodyFromCbor 48 | NewAlonzoTransactionOutputFromCbor = alonzo.NewAlonzoTransactionOutputFromCbor 49 | ) 50 | -------------------------------------------------------------------------------- /ledger/alonzo/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package alonzo 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/blinklabs-io/gouroboros/ledger/common" 21 | ) 22 | 23 | type ExUnitsTooBigUtxoError struct { 24 | TotalExUnits common.ExUnits 25 | MaxTxExUnits common.ExUnits 26 | } 27 | 28 | func (e ExUnitsTooBigUtxoError) Error() string { 29 | return fmt.Sprintf( 30 | "ExUnits too big: total %d/%d steps/memory, maximum %d/%d steps/memory", 31 | e.TotalExUnits.Steps, 32 | e.TotalExUnits.Memory, 33 | e.MaxTxExUnits.Steps, 34 | e.MaxTxExUnits.Memory, 35 | ) 36 | } 37 | 38 | type InsufficientCollateralError struct { 39 | Provided uint64 40 | Required uint64 41 | } 42 | 43 | func (e InsufficientCollateralError) Error() string { 44 | return fmt.Sprintf( 45 | "insufficient collateral: provided %d, required %d", 46 | e.Provided, 47 | e.Required, 48 | ) 49 | } 50 | 51 | type CollateralContainsNonAdaError struct { 52 | Provided uint64 53 | } 54 | 55 | func (e CollateralContainsNonAdaError) Error() string { 56 | return fmt.Sprintf( 57 | "collateral contains non-ADA: provided %d", 58 | e.Provided, 59 | ) 60 | } 61 | 62 | type NoCollateralInputsError struct{} 63 | 64 | func (NoCollateralInputsError) Error() string { 65 | return "no collateral inputs" 66 | } 67 | -------------------------------------------------------------------------------- /ledger/babbage.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ledger 16 | 17 | import "github.com/blinklabs-io/gouroboros/ledger/babbage" 18 | 19 | // The below are compatibility types, constants, and functions for the Babbage era 20 | // to keep existing code working after a refactor of the ledger package 21 | 22 | // Babbage types 23 | type ( 24 | BabbageBlock = babbage.BabbageBlock 25 | BabbageBlockHeader = babbage.BabbageBlockHeader 26 | BabbageTransaction = babbage.BabbageTransaction 27 | BabbageTransactionBody = babbage.BabbageTransactionBody 28 | BabbageTransactionOutput = babbage.BabbageTransactionOutput 29 | BabbageTransactionWitnessSet = babbage.BabbageTransactionWitnessSet 30 | BabbageProtocolParameters = babbage.BabbageProtocolParameters 31 | BabbageProtocolParameterUpdate = babbage.BabbageProtocolParameterUpdate 32 | ) 33 | 34 | // Babbage constants 35 | const ( 36 | EraIdBabbage = babbage.EraIdBabbage 37 | BlockTypeBabbage = babbage.BlockTypeBabbage 38 | BlockHeaderTypeBabbage = babbage.BlockHeaderTypeBabbage 39 | TxTypeBabbage = babbage.TxTypeBabbage 40 | ) 41 | 42 | // Babbage functions 43 | var ( 44 | NewBabbageBlockFromCbor = babbage.NewBabbageBlockFromCbor 45 | NewBabbageBlockHeaderFromCbor = babbage.NewBabbageBlockHeaderFromCbor 46 | NewBabbageTransactionFromCbor = babbage.NewBabbageTransactionFromCbor 47 | NewBabbageTransactionBodyFromCbor = babbage.NewBabbageTransactionBodyFromCbor 48 | NewBabbageTransactionOutputFromCbor = babbage.NewBabbageTransactionOutputFromCbor 49 | ) 50 | -------------------------------------------------------------------------------- /ledger/babbage/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package babbage 16 | 17 | import ( 18 | "fmt" 19 | ) 20 | 21 | type TooManyCollateralInputsError struct { 22 | Provided uint 23 | Max uint 24 | } 25 | 26 | func (e TooManyCollateralInputsError) Error() string { 27 | return fmt.Sprintf( 28 | "too many collateral inputs: provided %d, maximum %d", 29 | e.Provided, 30 | e.Max, 31 | ) 32 | } 33 | 34 | type IncorrectTotalCollateralFieldError struct { 35 | Provided uint64 36 | TotalCollateral uint64 37 | } 38 | 39 | func (e IncorrectTotalCollateralFieldError) Error() string { 40 | return fmt.Sprintf( 41 | "incorrect total collateral field: provided %d, total collateral %d", 42 | e.Provided, 43 | e.TotalCollateral, 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /ledger/block.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ledger 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | 21 | "github.com/blinklabs-io/gouroboros/ledger/common" 22 | ) 23 | 24 | // Compatibility aliases 25 | type ( 26 | Block = common.Block 27 | BlockHeader = common.BlockHeader 28 | ) 29 | 30 | func NewBlockFromCbor(blockType uint, data []byte) (Block, error) { 31 | switch blockType { 32 | case BlockTypeByronEbb: 33 | return NewByronEpochBoundaryBlockFromCbor(data) 34 | case BlockTypeByronMain: 35 | return NewByronMainBlockFromCbor(data) 36 | case BlockTypeShelley: 37 | return NewShelleyBlockFromCbor(data) 38 | case BlockTypeAllegra: 39 | return NewAllegraBlockFromCbor(data) 40 | case BlockTypeMary: 41 | return NewMaryBlockFromCbor(data) 42 | case BlockTypeAlonzo: 43 | return NewAlonzoBlockFromCbor(data) 44 | case BlockTypeBabbage: 45 | return NewBabbageBlockFromCbor(data) 46 | case BlockTypeConway: 47 | return NewConwayBlockFromCbor(data) 48 | } 49 | return nil, fmt.Errorf("unknown node-to-client block type: %d", blockType) 50 | } 51 | 52 | func NewBlockHeaderFromCbor(blockType uint, data []byte) (BlockHeader, error) { 53 | switch blockType { 54 | case BlockTypeByronEbb: 55 | return NewByronEpochBoundaryBlockHeaderFromCbor(data) 56 | case BlockTypeByronMain: 57 | return NewByronMainBlockHeaderFromCbor(data) 58 | case BlockTypeShelley: 59 | return NewShelleyBlockHeaderFromCbor(data) 60 | case BlockTypeAllegra: 61 | return NewAllegraBlockHeaderFromCbor(data) 62 | case BlockTypeMary: 63 | return NewMaryBlockHeaderFromCbor(data) 64 | case BlockTypeAlonzo: 65 | return NewAlonzoBlockHeaderFromCbor(data) 66 | case BlockTypeBabbage: 67 | return NewBabbageBlockHeaderFromCbor(data) 68 | case BlockTypeConway: 69 | return NewConwayBlockHeaderFromCbor(data) 70 | default: 71 | return nil, fmt.Errorf("unknown node-to-node block type: %d", blockType) 72 | } 73 | } 74 | 75 | func DetermineBlockType(data []byte) (uint, error) { 76 | if _, err := NewByronEpochBoundaryBlockFromCbor(data); err == nil { 77 | return BlockTypeByronEbb, nil 78 | } 79 | if _, err := NewByronMainBlockFromCbor(data); err == nil { 80 | return BlockTypeByronMain, nil 81 | } 82 | if _, err := NewShelleyBlockFromCbor(data); err == nil { 83 | return BlockTypeShelley, nil 84 | } 85 | if _, err := NewAllegraBlockFromCbor(data); err == nil { 86 | return BlockTypeAllegra, nil 87 | } 88 | if _, err := NewMaryBlockFromCbor(data); err == nil { 89 | return BlockTypeMary, nil 90 | } 91 | if _, err := NewAlonzoBlockFromCbor(data); err == nil { 92 | return BlockTypeAlonzo, nil 93 | } 94 | if _, err := NewBabbageBlockFromCbor(data); err == nil { 95 | return BlockTypeBabbage, nil 96 | } 97 | if _, err := NewConwayBlockFromCbor(data); err == nil { 98 | return BlockTypeConway, nil 99 | } 100 | return 0, errors.New("unknown block type") 101 | } 102 | -------------------------------------------------------------------------------- /ledger/byron.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ledger 16 | 17 | import "github.com/blinklabs-io/gouroboros/ledger/byron" 18 | 19 | // The below are compatibility types, constants, and functions for the Byron era 20 | // to keep existing code working after a refactor of the ledger package 21 | 22 | // Byron types 23 | type ( 24 | ByronEpochBoundaryBlock = byron.ByronEpochBoundaryBlock 25 | ByronMainBlock = byron.ByronMainBlock 26 | ByronEpochBounaryBlockHeader = byron.ByronEpochBoundaryBlockHeader 27 | ByronMainBlockHeader = byron.ByronMainBlockHeader 28 | ByronTransaction = byron.ByronTransaction 29 | ByronTransactionInput = byron.ByronTransactionInput 30 | ByronTransactionOutput = byron.ByronTransactionOutput 31 | ) 32 | 33 | // Byron constants 34 | const ( 35 | EraIdByron = byron.EraIdByron 36 | BlockTypeByronEbb = byron.BlockTypeByronEbb 37 | BlockTypeByronMain = byron.BlockTypeByronMain 38 | BlockHeaderTypeByron = byron.BlockHeaderTypeByron 39 | TxTypeByron = byron.TxTypeByron 40 | ) 41 | 42 | // Byron functions 43 | var ( 44 | NewByronEpochBoundaryBlockFromCbor = byron.NewByronEpochBoundaryBlockFromCbor 45 | NewByronMainBlockFromCbor = byron.NewByronMainBlockFromCbor 46 | NewByronEpochBoundaryBlockHeaderFromCbor = byron.NewByronEpochBoundaryBlockHeaderFromCbor 47 | NewByronMainBlockHeaderFromCbor = byron.NewByronMainBlockHeaderFromCbor 48 | NewByronTransactionInput = byron.NewByronTransactionInput 49 | NewByronTransactionFromCbor = byron.NewByronTransactionFromCbor 50 | NewByronTransactionOutputFromCbor = byron.NewByronTransactionOutputFromCbor 51 | ) 52 | -------------------------------------------------------------------------------- /ledger/common/block.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import utxorpc "github.com/utxorpc/go-codegen/utxorpc/v1alpha/cardano" 4 | 5 | type Block interface { 6 | BlockHeader 7 | Header() BlockHeader 8 | Type() int 9 | Transactions() []Transaction 10 | Utxorpc() *utxorpc.Block 11 | } 12 | 13 | type BlockHeader interface { 14 | Hash() Blake2b256 15 | PrevHash() Blake2b256 16 | BlockNumber() uint64 17 | SlotNumber() uint64 18 | IssuerVkey() IssuerVkey 19 | BlockBodySize() uint64 20 | Era() Era 21 | Cbor() []byte 22 | } 23 | -------------------------------------------------------------------------------- /ledger/common/credentials.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package common 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/blinklabs-io/gouroboros/cbor" 21 | utxorpc "github.com/utxorpc/go-codegen/utxorpc/v1alpha/cardano" 22 | "golang.org/x/crypto/blake2b" 23 | ) 24 | 25 | const ( 26 | CredentialTypeAddrKeyHash = 0 27 | CredentialTypeScriptHash = 1 28 | ) 29 | 30 | type CredentialHash = Blake2b224 31 | 32 | type Credential struct { 33 | cbor.StructAsArray 34 | cbor.DecodeStoreCbor 35 | CredType uint 36 | Credential CredentialHash 37 | } 38 | 39 | func (c *Credential) UnmarshalCBOR(cborData []byte) error { 40 | type tCredential Credential 41 | var tmp tCredential 42 | if _, err := cbor.Decode(cborData, &tmp); err != nil { 43 | return err 44 | } 45 | *c = Credential(tmp) 46 | c.SetCbor(cborData) 47 | return nil 48 | } 49 | 50 | func (c *Credential) Hash() Blake2b224 { 51 | hash, err := blake2b.New(28, nil) 52 | if err != nil { 53 | panic( 54 | fmt.Sprintf( 55 | "unexpected error creating empty blake2b hash: %s", 56 | err, 57 | ), 58 | ) 59 | } 60 | if c != nil { 61 | hash.Write(c.Credential[:]) 62 | } 63 | return Blake2b224(hash.Sum(nil)) 64 | } 65 | 66 | func (c *Credential) Utxorpc() *utxorpc.StakeCredential { 67 | ret := &utxorpc.StakeCredential{} 68 | switch c.CredType { 69 | case CredentialTypeAddrKeyHash: 70 | ret.StakeCredential = &utxorpc.StakeCredential_AddrKeyHash{ 71 | AddrKeyHash: c.Credential[:], 72 | } 73 | case CredentialTypeScriptHash: 74 | ret.StakeCredential = &utxorpc.StakeCredential_ScriptHash{ 75 | ScriptHash: c.Credential[:], 76 | } 77 | } 78 | return ret 79 | } 80 | -------------------------------------------------------------------------------- /ledger/common/era.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package common 16 | 17 | type Era struct { 18 | Id uint8 19 | Name string 20 | } 21 | 22 | var EraInvalid = Era{ 23 | Id: 0, 24 | Name: "invalid", 25 | } 26 | 27 | var eras map[uint8]Era 28 | 29 | func RegisterEra(era Era) { 30 | if eras == nil { 31 | eras = make(map[uint8]Era) 32 | } 33 | eras[era.Id] = era 34 | } 35 | 36 | func EraById(eraId uint8) Era { 37 | era, ok := eras[eraId] 38 | if !ok { 39 | return EraInvalid 40 | } 41 | return era 42 | } 43 | -------------------------------------------------------------------------------- /ledger/common/era_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package common_test 16 | 17 | import ( 18 | "testing" 19 | 20 | _ "github.com/blinklabs-io/gouroboros/ledger" // This is needed to get the eras registered 21 | "github.com/blinklabs-io/gouroboros/ledger/common" 22 | ) 23 | 24 | type getEraByIdTestDefinition struct { 25 | Id uint8 26 | Name string 27 | } 28 | 29 | var getEraByIdTests = []getEraByIdTestDefinition{ 30 | { 31 | Id: 0, 32 | Name: "Byron", 33 | }, 34 | { 35 | Id: 1, 36 | Name: "Shelley", 37 | }, 38 | { 39 | Id: 2, 40 | Name: "Allegra", 41 | }, 42 | { 43 | Id: 3, 44 | Name: "Mary", 45 | }, 46 | { 47 | Id: 4, 48 | Name: "Alonzo", 49 | }, 50 | { 51 | Id: 5, 52 | Name: "Babbage", 53 | }, 54 | { 55 | Id: 6, 56 | Name: "Conway", 57 | }, 58 | { 59 | Id: 99, 60 | Name: "invalid", 61 | }, 62 | } 63 | 64 | func TestGetEraById(t *testing.T) { 65 | for _, test := range getEraByIdTests { 66 | era := common.EraById(test.Id) 67 | if era == common.EraInvalid { 68 | if test.Name != "invalid" { 69 | t.Fatalf("got unexpected EraInvalid, wanted %s", test.Name) 70 | } 71 | } else { 72 | if era.Name != test.Name { 73 | t.Fatalf("did not get expected era name for ID %d, got: %s, wanted: %s", test.Id, era.Name, test.Name) 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /ledger/common/genesis.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package common 16 | 17 | import ( 18 | "fmt" 19 | "math/big" 20 | ) 21 | 22 | // GenesisRat is a wrapper to big.Rat that allows for unmarshaling from a bare float from JSON 23 | type GenesisRat struct { 24 | *big.Rat 25 | } 26 | 27 | func (r *GenesisRat) UnmarshalJSON(data []byte) error { 28 | r.Rat = new(big.Rat) 29 | if _, ok := r.SetString(string(data)); !ok { 30 | return fmt.Errorf("math/big: cannot unmarshal %q into a *big.Rat", data) 31 | } 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /ledger/common/native_script.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package common 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/blinklabs-io/gouroboros/cbor" 21 | ) 22 | 23 | type NativeScript struct { 24 | item any 25 | } 26 | 27 | func (n *NativeScript) Item() any { 28 | return n.item 29 | } 30 | 31 | func (n *NativeScript) UnmarshalCBOR(data []byte) error { 32 | id, err := cbor.DecodeIdFromList(data) 33 | if err != nil { 34 | return err 35 | } 36 | var tmpData any 37 | switch id { 38 | case 0: 39 | tmpData = &NativeScriptPubkey{} 40 | case 1: 41 | tmpData = &NativeScriptAll{} 42 | case 2: 43 | tmpData = &NativeScriptAny{} 44 | case 3: 45 | tmpData = &NativeScriptNofK{} 46 | case 4: 47 | tmpData = &NativeScriptInvalidBefore{} 48 | case 5: 49 | tmpData = &NativeScriptInvalidHereafter{} 50 | default: 51 | return fmt.Errorf("unknown native script type %d", id) 52 | } 53 | if _, err := cbor.Decode(data, tmpData); err != nil { 54 | return err 55 | } 56 | return nil 57 | } 58 | 59 | type NativeScriptPubkey struct { 60 | cbor.StructAsArray 61 | Type uint 62 | Hash []byte 63 | } 64 | 65 | type NativeScriptAll struct { 66 | cbor.StructAsArray 67 | Type uint 68 | Scripts []NativeScript 69 | } 70 | 71 | type NativeScriptAny struct { 72 | cbor.StructAsArray 73 | Type uint 74 | Scripts []NativeScript 75 | } 76 | 77 | type NativeScriptNofK struct { 78 | cbor.StructAsArray 79 | Type uint 80 | N uint 81 | Scripts []NativeScript 82 | } 83 | 84 | type NativeScriptInvalidBefore struct { 85 | cbor.StructAsArray 86 | Type uint 87 | Slot uint64 88 | } 89 | 90 | type NativeScriptInvalidHereafter struct { 91 | cbor.StructAsArray 92 | Type uint 93 | Slot uint64 94 | } 95 | -------------------------------------------------------------------------------- /ledger/common/nonce.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package common 16 | 17 | import ( 18 | "encoding/json" 19 | "errors" 20 | "fmt" 21 | "slices" 22 | 23 | "github.com/blinklabs-io/gouroboros/cbor" 24 | ) 25 | 26 | const ( 27 | NonceTypeNeutral = 0 28 | NonceTypeNonce = 1 29 | ) 30 | 31 | type Nonce struct { 32 | cbor.StructAsArray 33 | Type uint 34 | Value [32]byte 35 | } 36 | 37 | func (n *Nonce) UnmarshalCBOR(data []byte) error { 38 | nonceType, err := cbor.DecodeIdFromList(data) 39 | if err != nil { 40 | return err 41 | } 42 | // nonce type is known within uint range 43 | n.Type = uint(nonceType) // #nosec G115 44 | switch nonceType { 45 | case NonceTypeNeutral: 46 | // Value uses default value 47 | case NonceTypeNonce: 48 | type tNonce Nonce 49 | var tmp tNonce 50 | if _, err := cbor.Decode(data, &tmp); err != nil { 51 | return err 52 | } 53 | *n = Nonce(tmp) 54 | default: 55 | return fmt.Errorf("unsupported nonce type %d", nonceType) 56 | } 57 | return nil 58 | } 59 | 60 | func (n *Nonce) UnmarshalJSON(data []byte) error { 61 | var tmpData map[string]string 62 | if err := json.Unmarshal(data, &tmpData); err != nil { 63 | return err 64 | } 65 | tag, ok := tmpData["tag"] 66 | if !ok { 67 | return errors.New("did not find expected key 'tag' for nonce") 68 | } 69 | switch tag { 70 | case "NeutralNonce": 71 | n.Type = NonceTypeNeutral 72 | default: 73 | return fmt.Errorf("unsupported nonce tag: %s", tag) 74 | } 75 | return nil 76 | } 77 | 78 | func (n *Nonce) MarshalCBOR() ([]byte, error) { 79 | var tmpData []any 80 | switch n.Type { 81 | case NonceTypeNeutral: 82 | tmpData = []any{NonceTypeNeutral} 83 | case NonceTypeNonce: 84 | tmpData = []any{NonceTypeNonce, n.Value} 85 | } 86 | return cbor.Encode(tmpData) 87 | } 88 | 89 | // CalculateRollingNonce calculates a rolling nonce (eta_v) value from the previous block's eta_v value and the current 90 | // block's VRF result 91 | func CalculateRollingNonce( 92 | prevBlockNonce []byte, 93 | blockVrf []byte, 94 | ) (Blake2b256, error) { 95 | if len(blockVrf) != 32 && len(blockVrf) != 64 { 96 | return Blake2b256{}, fmt.Errorf( 97 | "invalid block VRF length: %d, expected 32 or 64", 98 | len(blockVrf), 99 | ) 100 | } 101 | blockVrfHash := Blake2b256Hash(blockVrf) 102 | tmpData := slices.Concat(prevBlockNonce, blockVrfHash.Bytes()) 103 | return Blake2b256Hash(tmpData), nil 104 | } 105 | 106 | // CalculateEpochNonce calculates an epoch nonce from the rolling nonce (eta_v) value of the block immediately before the stability 107 | // window and the block hash of the first block from the previous epoch. 108 | func CalculateEpochNonce( 109 | stableBlockNonce []byte, 110 | prevEpochFirstBlockHash []byte, 111 | extraEntropy []byte, 112 | ) (Blake2b256, error) { 113 | tmpData := slices.Concat( 114 | stableBlockNonce, 115 | prevEpochFirstBlockHash, 116 | ) 117 | tmpDataHash := Blake2b256Hash(tmpData) 118 | if len(extraEntropy) > 0 { 119 | tmpData2 := slices.Concat( 120 | tmpDataHash.Bytes(), 121 | extraEntropy, 122 | ) 123 | tmpDataHash = Blake2b256Hash(tmpData2) 124 | } 125 | return tmpDataHash, nil 126 | } 127 | -------------------------------------------------------------------------------- /ledger/common/pparams.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package common 16 | 17 | import ( 18 | "log/slog" 19 | 20 | "github.com/blinklabs-io/gouroboros/cbor" 21 | "github.com/utxorpc/go-codegen/utxorpc/v1alpha/cardano" 22 | ) 23 | 24 | type ProtocolParameterUpdate interface { 25 | IsProtocolParameterUpdate() 26 | Cbor() []byte 27 | } 28 | 29 | type ProtocolParametersProtocolVersion struct { 30 | cbor.StructAsArray 31 | Major uint 32 | Minor uint 33 | } 34 | 35 | type ProtocolParameters interface { 36 | Utxorpc() *cardano.PParams 37 | } 38 | 39 | type ExUnitPrice struct { 40 | cbor.StructAsArray 41 | MemPrice *cbor.Rat 42 | StepPrice *cbor.Rat 43 | } 44 | 45 | // ConvertToUtxorpcCardanoCostModels converts a map of cost models for Plutus scripts into cardano.CostModels 46 | // Only PlutusV(keys 1, 2, and 3) are supported. 47 | func ConvertToUtxorpcCardanoCostModels( 48 | models map[uint][]int64, 49 | ) *cardano.CostModels { 50 | costModels := &cardano.CostModels{} 51 | for k, v := range models { 52 | costModel := &cardano.CostModel{Values: v} 53 | switch k { 54 | case 1: 55 | costModels.PlutusV1 = costModel 56 | case 2: 57 | costModels.PlutusV2 = costModel 58 | case 3: 59 | costModels.PlutusV3 = costModel 60 | default: 61 | slog.Warn("unsupported cost model version", "version", k) 62 | } 63 | } 64 | return costModels 65 | } 66 | -------------------------------------------------------------------------------- /ledger/common/rules.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package common 16 | 17 | type UtxoValidationRuleFunc func(Transaction, uint64, LedgerState, ProtocolParameters) error 18 | -------------------------------------------------------------------------------- /ledger/common/state.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package common 16 | 17 | import ( 18 | pcommon "github.com/blinklabs-io/gouroboros/protocol/common" 19 | ) 20 | 21 | // UtxoState defines the interface for querying the UTxO state 22 | type UtxoState interface { 23 | UtxoById(TransactionInput) (Utxo, error) 24 | } 25 | 26 | // CertState defines the interface for querying the certificate state 27 | type CertState interface { 28 | StakeRegistration([]byte) ([]StakeRegistrationCertificate, error) 29 | PoolRegistration([]byte) ([]PoolRegistrationCertificate, error) 30 | } 31 | 32 | // LedgerState defines the interface for querying the ledger 33 | type LedgerState interface { 34 | UtxoState 35 | CertState 36 | NetworkId() uint 37 | } 38 | 39 | // TipState defines the interface for querying the current tip 40 | type TipState interface { 41 | Tip() (pcommon.Tip, error) 42 | } 43 | -------------------------------------------------------------------------------- /ledger/common/vrf.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package common 16 | 17 | import ( 18 | "github.com/blinklabs-io/gouroboros/cbor" 19 | ) 20 | 21 | type VrfResult struct { 22 | cbor.StructAsArray 23 | Output []byte 24 | Proof []byte 25 | } 26 | -------------------------------------------------------------------------------- /ledger/common/witness.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package common 16 | 17 | import ( 18 | "github.com/blinklabs-io/gouroboros/cbor" 19 | ) 20 | 21 | type RedeemerTag uint8 22 | 23 | const ( 24 | RedeemerTagSpend RedeemerTag = 0 25 | RedeemerTagMint RedeemerTag = 1 26 | RedeemerTagCert RedeemerTag = 2 27 | RedeemerTagReward RedeemerTag = 3 28 | RedeemerTagVoting RedeemerTag = 4 29 | RedeemerTagProposing RedeemerTag = 5 30 | ) 31 | 32 | type VkeyWitness struct { 33 | cbor.StructAsArray 34 | Vkey []byte 35 | Signature []byte 36 | } 37 | 38 | type BootstrapWitness struct { 39 | cbor.StructAsArray 40 | PublicKey []byte 41 | Signature []byte 42 | ChainCode []byte 43 | Attributes []byte 44 | } 45 | -------------------------------------------------------------------------------- /ledger/compat.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ledger 16 | 17 | import ( 18 | "github.com/blinklabs-io/gouroboros/ledger/common" 19 | ) 20 | 21 | // The below are compatibility types and functions to keep existing code working 22 | // after a refactor of the ledger package 23 | 24 | // Hash types 25 | type ( 26 | Blake2b224 = common.Blake2b224 27 | Blake2b256 = common.Blake2b256 28 | ) 29 | 30 | func NewBlake2b224(data []byte) Blake2b224 { 31 | return common.NewBlake2b224(data) 32 | } 33 | 34 | func NewBlake2b256(data []byte) Blake2b256 { 35 | return common.NewBlake2b256(data) 36 | } 37 | 38 | // Address 39 | type ( 40 | Address = common.Address 41 | AddrKeyHash = common.AddrKeyHash 42 | ) 43 | 44 | const ( 45 | AddressTypeScriptNone = common.AddressTypeScriptNone 46 | AddressTypeKeyNone = common.AddressTypeKeyNone 47 | AddressTypeKeyKey = common.AddressTypeKeyKey 48 | ) 49 | 50 | var ( 51 | NewAddress = common.NewAddress 52 | NewAddressFromParts = common.NewAddressFromParts 53 | ) 54 | 55 | // Governance types 56 | type ( 57 | VotingProcedure = common.VotingProcedure 58 | VotingProcedures = common.VotingProcedures 59 | ProposalProcedure = common.ProposalProcedure 60 | ) 61 | 62 | // Certificates 63 | type ( 64 | Certificate = common.Certificate 65 | CertificateWrapper = common.CertificateWrapper 66 | PoolRetirementCertificate = common.PoolRetirementCertificate 67 | PoolRegistrationCertificate = common.PoolRegistrationCertificate 68 | StakeDelegationCertificate = common.StakeDelegationCertificate 69 | ) 70 | 71 | // Other types 72 | type IssuerVkey = common.IssuerVkey 73 | 74 | // Pools 75 | type ( 76 | PoolRelay = common.PoolRelay 77 | PoolId = common.PoolId 78 | ) 79 | 80 | func NewPoolIdFromBech32(poolId string) (PoolId, error) { 81 | return common.NewPoolIdFromBech32(poolId) 82 | } 83 | 84 | // Assets 85 | type ( 86 | MultiAssetTypeMint = common.MultiAssetTypeMint 87 | MultiAssetTypeOutput = common.MultiAssetTypeOutput 88 | AssetFingerprint = common.AssetFingerprint 89 | ) 90 | 91 | func NewAssetFingerprint(policyId []byte, assetName []byte) AssetFingerprint { 92 | return common.NewAssetFingerprint(policyId, assetName) 93 | } 94 | -------------------------------------------------------------------------------- /ledger/conway.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ledger 16 | 17 | import "github.com/blinklabs-io/gouroboros/ledger/conway" 18 | 19 | // The below are compatibility types, constants, and functions for the Conway era 20 | // to keep existing code working after a refactor of the ledger package 21 | 22 | // Conway types 23 | type ( 24 | ConwayBlock = conway.ConwayBlock 25 | ConwayBlockHeader = conway.ConwayBlockHeader 26 | ConwayTransaction = conway.ConwayTransaction 27 | ConwayTransactionBody = conway.ConwayTransactionBody 28 | ConwayTransactionWitnessSet = conway.ConwayTransactionWitnessSet 29 | ConwayProtocolParameters = conway.ConwayProtocolParameters 30 | ConwayProtocolParameterUpdate = conway.ConwayProtocolParameterUpdate 31 | ) 32 | 33 | // Conway constants 34 | const ( 35 | EraIdConway = conway.EraIdConway 36 | BlockTypeConway = conway.BlockTypeConway 37 | BlockHeaderTypeConway = conway.BlockHeaderTypeConway 38 | TxTypeConway = conway.TxTypeConway 39 | ) 40 | 41 | // Conway functions 42 | var ( 43 | NewConwayBlockFromCbor = conway.NewConwayBlockFromCbor 44 | NewConwayBlockHeaderFromCbor = conway.NewConwayBlockHeaderFromCbor 45 | NewConwayTransactionFromCbor = conway.NewConwayTransactionFromCbor 46 | NewConwayTransactionBodyFromCbor = conway.NewConwayTransactionBodyFromCbor 47 | ) 48 | -------------------------------------------------------------------------------- /ledger/conway/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package conway 16 | 17 | import ( 18 | "strings" 19 | 20 | "github.com/blinklabs-io/gouroboros/ledger/common" 21 | ) 22 | 23 | type NonDisjointRefInputsError struct { 24 | Inputs []common.TransactionInput 25 | } 26 | 27 | func (e NonDisjointRefInputsError) Error() string { 28 | tmpInputs := make([]string, len(e.Inputs)) 29 | for idx, tmpInput := range e.Inputs { 30 | tmpInputs[idx] = tmpInput.String() 31 | } 32 | return "non-disjoint reference inputs: " + strings.Join(tmpInputs, ", ") 33 | } 34 | -------------------------------------------------------------------------------- /ledger/conway/genesis.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package conway 16 | 17 | import ( 18 | "encoding/json" 19 | "io" 20 | "os" 21 | 22 | "github.com/blinklabs-io/gouroboros/ledger/common" 23 | ) 24 | 25 | type ConwayGenesis struct { 26 | PoolVotingThresholds ConwayGenesisPoolVotingThresholds `json:"poolVotingThresholds"` 27 | DRepVotingThresholds ConwayGenesisDRepVotingThresholds `json:"dRepVotingThresholds"` 28 | MinCommitteeSize uint `json:"committeeMinSize"` 29 | CommitteeTermLimit uint64 `json:"committeeMaxTermLength"` 30 | GovActionValidityPeriod uint64 `json:"govActionLifetime"` 31 | GovActionDeposit uint64 `json:"govActionDeposit"` 32 | DRepDeposit uint64 `json:"dRepDeposit"` 33 | DRepInactivityPeriod uint64 `json:"dRepActivity"` 34 | MinFeeRefScriptCostPerByte *common.GenesisRat `json:"minFeeRefScriptCostPerByte"` 35 | PlutusV3CostModel []int64 `json:"plutusV3CostModel"` 36 | Constitution ConwayGenesisConstitution `json:"constitution"` 37 | Committee ConwayGenesisCommittee `json:"committee"` 38 | } 39 | 40 | type ConwayGenesisPoolVotingThresholds struct { 41 | CommitteeNormal *common.GenesisRat `json:"committeeNormal"` 42 | CommitteeNoConfidence *common.GenesisRat `json:"committeeNoConfidence"` 43 | HardForkInitiation *common.GenesisRat `json:"hardForkInitiation"` 44 | MotionNoConfidence *common.GenesisRat `json:"motionNoConfidence"` 45 | PpSecurityGroup *common.GenesisRat `json:"ppSecurityGroup"` 46 | } 47 | 48 | type ConwayGenesisDRepVotingThresholds struct { 49 | MotionNoConfidence *common.GenesisRat `json:"motionNoConfidence"` 50 | CommitteeNormal *common.GenesisRat `json:"committeeNormal"` 51 | CommitteeNoConfidence *common.GenesisRat `json:"committeeNoConfidence"` 52 | UpdateToConstitution *common.GenesisRat `json:"updateToConstitution"` 53 | HardForkInitiation *common.GenesisRat `json:"hardForkInitiation"` 54 | PpNetworkGroup *common.GenesisRat `json:"ppNetworkGroup"` 55 | PpEconomicGroup *common.GenesisRat `json:"ppEconomicGroup"` 56 | PpTechnicalGroup *common.GenesisRat `json:"ppTechnicalGroup"` 57 | PpGovGroup *common.GenesisRat `json:"ppGovGroup"` 58 | TreasuryWithdrawal *common.GenesisRat `json:"treasuryWithdrawal"` 59 | } 60 | 61 | type ConwayGenesisConstitution struct { 62 | Anchor ConwayGenesisConstitutionAnchor `json:"anchor"` 63 | Script string `json:"script"` 64 | } 65 | 66 | type ConwayGenesisConstitutionAnchor struct { 67 | DataHash string `json:"dataHash"` 68 | Url string `json:"url"` 69 | } 70 | 71 | type ConwayGenesisCommittee struct { 72 | Members map[string]int `json:"members"` 73 | Threshold map[string]int `json:"threshold"` 74 | } 75 | 76 | func NewConwayGenesisFromReader(r io.Reader) (ConwayGenesis, error) { 77 | var ret ConwayGenesis 78 | dec := json.NewDecoder(r) 79 | dec.DisallowUnknownFields() 80 | if err := dec.Decode(&ret); err != nil { 81 | return ret, err 82 | } 83 | return ret, nil 84 | } 85 | 86 | func NewConwayGenesisFromFile(path string) (ConwayGenesis, error) { 87 | f, err := os.Open(path) 88 | if err != nil { 89 | return ConwayGenesis{}, err 90 | } 91 | defer f.Close() 92 | return NewConwayGenesisFromReader(f) 93 | } 94 | -------------------------------------------------------------------------------- /ledger/era.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ledger 16 | 17 | import ( 18 | "github.com/blinklabs-io/gouroboros/ledger/common" 19 | ) 20 | 21 | type Era = common.Era 22 | 23 | var EraInvalid = common.EraInvalid 24 | 25 | func GetEraById(eraId uint8) Era { 26 | return common.EraById(eraId) 27 | } 28 | 29 | // BlockHeaderToBlockTypeMap is a mapping of NtN chainsync block header types 30 | // (era ID) to NtC block types 31 | var BlockHeaderToBlockTypeMap = map[uint]uint{ 32 | BlockHeaderTypeShelley: BlockTypeShelley, 33 | BlockHeaderTypeAllegra: BlockTypeAllegra, 34 | BlockHeaderTypeMary: BlockTypeMary, 35 | BlockHeaderTypeAlonzo: BlockTypeAlonzo, 36 | BlockHeaderTypeBabbage: BlockTypeBabbage, 37 | BlockHeaderTypeConway: BlockTypeConway, 38 | } 39 | 40 | // BlockToBlockHeaderTypeMap is a mapping of NtC chainsync block types 41 | // to NtN block header types (era ID) 42 | var BlockToBlockHeaderTypeMap = map[uint]uint{ 43 | BlockTypeShelley: BlockHeaderTypeShelley, 44 | BlockTypeAllegra: BlockHeaderTypeAllegra, 45 | BlockTypeMary: BlockHeaderTypeMary, 46 | BlockTypeAlonzo: BlockHeaderTypeAlonzo, 47 | BlockTypeBabbage: BlockHeaderTypeBabbage, 48 | BlockTypeConway: BlockHeaderTypeConway, 49 | } 50 | -------------------------------------------------------------------------------- /ledger/mary.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ledger 16 | 17 | import "github.com/blinklabs-io/gouroboros/ledger/mary" 18 | 19 | // The below are compatibility types, constants, and functions for the Mary era 20 | // to keep existing code working after a refactor of the ledger package 21 | 22 | // Mary types 23 | type ( 24 | MaryBlock = mary.MaryBlock 25 | MaryBlockHeader = mary.MaryBlockHeader 26 | MaryTransaction = mary.MaryTransaction 27 | MaryTransactionBody = mary.MaryTransactionBody 28 | MaryTransactionOutput = mary.MaryTransactionOutput 29 | MaryTransactionOutputValue = mary.MaryTransactionOutputValue 30 | MaryProtocolParameters = mary.MaryProtocolParameters 31 | MaryProtocolParameterUpdate = mary.MaryProtocolParameterUpdate 32 | ) 33 | 34 | // Mary constants 35 | const ( 36 | EraIdMary = mary.EraIdMary 37 | BlockTypeMary = mary.BlockTypeMary 38 | BlockHeaderTypeMary = mary.BlockHeaderTypeMary 39 | TxTypeMary = mary.TxTypeMary 40 | ) 41 | 42 | // Mary functions 43 | var ( 44 | NewMaryBlockFromCbor = mary.NewMaryBlockFromCbor 45 | NewMaryBlockHeaderFromCbor = mary.NewMaryBlockHeaderFromCbor 46 | NewMaryTransactionFromCbor = mary.NewMaryTransactionFromCbor 47 | NewMaryTransactionBodyFromCbor = mary.NewMaryTransactionBodyFromCbor 48 | NewMaryTransactionOutputFromCbor = mary.NewMaryTransactionOutputFromCbor 49 | ) 50 | -------------------------------------------------------------------------------- /ledger/mary/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mary 16 | 17 | import ( 18 | "fmt" 19 | "strings" 20 | 21 | "github.com/blinklabs-io/gouroboros/ledger/common" 22 | ) 23 | 24 | type OutputTooBigUtxoError struct { 25 | Outputs []common.TransactionOutput 26 | } 27 | 28 | func (e OutputTooBigUtxoError) Error() string { 29 | tmpOutputs := make([]string, len(e.Outputs)) 30 | for idx, tmpOutput := range e.Outputs { 31 | tmpOutputs[idx] = fmt.Sprintf("%#v", tmpOutput) 32 | } 33 | return "output value too large: " + strings.Join(tmpOutputs, ", ") 34 | } 35 | -------------------------------------------------------------------------------- /ledger/mary/mary_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mary 16 | 17 | import ( 18 | "encoding/hex" 19 | "reflect" 20 | "testing" 21 | 22 | "github.com/blinklabs-io/gouroboros/cbor" 23 | "github.com/blinklabs-io/gouroboros/internal/test" 24 | "github.com/blinklabs-io/gouroboros/ledger/common" 25 | ) 26 | 27 | func createMaryTransactionOutputValueAssets( 28 | policyId []byte, 29 | assetName []byte, 30 | amount uint64, 31 | ) *common.MultiAsset[common.MultiAssetTypeOutput] { 32 | data := map[common.Blake2b224]map[cbor.ByteString]uint64{} 33 | policyIdKey := common.Blake2b224{} 34 | copy(policyIdKey[:], policyId) 35 | assetKey := cbor.NewByteString(assetName) 36 | data[policyIdKey] = map[cbor.ByteString]uint64{ 37 | assetKey: amount, 38 | } 39 | ret := common.NewMultiAsset[common.MultiAssetTypeOutput](data) 40 | return &ret 41 | } 42 | 43 | func TestMaryTransactionOutputValueEncodeDecode(t *testing.T) { 44 | var tests = []struct { 45 | CborHex string 46 | Object any 47 | }{ 48 | { 49 | CborHex: "1a02d71996", 50 | Object: MaryTransactionOutputValue{Amount: 47651222}, 51 | }, 52 | { 53 | CborHex: "1b0000000129d2de56", 54 | Object: MaryTransactionOutputValue{Amount: 4996652630}, 55 | }, 56 | { 57 | CborHex: "821a003d0900a1581c00000002df633853f6a47465c9496721d2d5b1291b8398016c0e87aea1476e7574636f696e01", 58 | // [4000000, {h'00000002DF633853F6A47465C9496721D2D5B1291B8398016C0E87AE': {h'6E7574636F696E': 1}}] 59 | Object: MaryTransactionOutputValue{ 60 | Amount: 4000000, 61 | Assets: createMaryTransactionOutputValueAssets( 62 | test.DecodeHexString( 63 | "00000002DF633853F6A47465C9496721D2D5B1291B8398016C0E87AE", 64 | ), 65 | test.DecodeHexString("6E7574636F696E"), 66 | 1, 67 | ), 68 | }, 69 | }, 70 | { 71 | CborHex: "821a004986e3a1581c3a9241cd79895e3a8d65261b40077d4437ce71e9d7c8c6c00e3f658ea1494669727374636f696e01", 72 | // [4818659, {h'3A9241CD79895E3A8D65261B40077D4437CE71E9D7C8C6C00E3F658E': {h'4669727374636F696E': 1}}] 73 | Object: MaryTransactionOutputValue{ 74 | Amount: 4818659, 75 | Assets: createMaryTransactionOutputValueAssets( 76 | test.DecodeHexString( 77 | "3A9241CD79895E3A8D65261B40077D4437CE71E9D7C8C6C00E3F658E", 78 | ), 79 | test.DecodeHexString("4669727374636F696E"), 80 | 1, 81 | ), 82 | }, 83 | }, 84 | } 85 | for _, test := range tests { 86 | // Test decode 87 | cborData, err := hex.DecodeString(test.CborHex) 88 | if err != nil { 89 | t.Fatalf("failed to decode CBOR hex: %s", err) 90 | } 91 | tmpObj := MaryTransactionOutputValue{} 92 | _, err = cbor.Decode(cborData, &tmpObj) 93 | if err != nil { 94 | t.Fatalf("failed to decode CBOR: %s", err) 95 | } 96 | if !reflect.DeepEqual(tmpObj, test.Object) { 97 | t.Fatalf( 98 | "CBOR did not decode to expected object\n got: %#v\n wanted: %#v", 99 | tmpObj, 100 | test.Object, 101 | ) 102 | } 103 | // Test encode 104 | cborData, err = cbor.Encode(test.Object) 105 | if err != nil { 106 | t.Fatalf("failed to encode object to CBOR: %s", err) 107 | } 108 | cborHex := hex.EncodeToString(cborData) 109 | if cborHex != test.CborHex { 110 | t.Fatalf( 111 | "object did not encode to expected CBOR\n got: %s\n wanted: %s", 112 | cborHex, 113 | test.CborHex, 114 | ) 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /ledger/mary/pparams.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mary 16 | 17 | import "github.com/blinklabs-io/gouroboros/ledger/shelley" 18 | 19 | type MaryProtocolParameters = shelley.ShelleyProtocolParameters 20 | 21 | type MaryProtocolParameterUpdate = shelley.ShelleyProtocolParameterUpdate 22 | 23 | func UpgradePParams( 24 | prevPParams shelley.ShelleyProtocolParameters, 25 | ) MaryProtocolParameters { 26 | return MaryProtocolParameters(prevPParams) 27 | } 28 | -------------------------------------------------------------------------------- /ledger/mary/pparams_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mary_test 16 | 17 | import ( 18 | "encoding/hex" 19 | "math/big" 20 | "reflect" 21 | "testing" 22 | 23 | "github.com/blinklabs-io/gouroboros/cbor" 24 | "github.com/blinklabs-io/gouroboros/ledger/mary" 25 | "github.com/utxorpc/go-codegen/utxorpc/v1alpha/cardano" 26 | ) 27 | 28 | func TestMaryProtocolParamsUpdate(t *testing.T) { 29 | testDefs := []struct { 30 | startParams mary.MaryProtocolParameters 31 | updateCbor string 32 | expectedParams mary.MaryProtocolParameters 33 | }{ 34 | { 35 | startParams: mary.MaryProtocolParameters{ 36 | Decentralization: &cbor.Rat{ 37 | Rat: new(big.Rat).SetInt64(1), 38 | }, 39 | }, 40 | updateCbor: "a10cd81e82090a", 41 | expectedParams: mary.MaryProtocolParameters{ 42 | Decentralization: &cbor.Rat{Rat: big.NewRat(9, 10)}, 43 | }, 44 | }, 45 | { 46 | startParams: mary.MaryProtocolParameters{ 47 | ProtocolMajor: 4, 48 | }, 49 | updateCbor: "a10e820500", 50 | expectedParams: mary.MaryProtocolParameters{ 51 | ProtocolMajor: 5, 52 | }, 53 | }, 54 | } 55 | for _, testDef := range testDefs { 56 | cborBytes, err := hex.DecodeString(testDef.updateCbor) 57 | if err != nil { 58 | t.Fatalf("unexpected error: %s", err) 59 | } 60 | var tmpUpdate mary.MaryProtocolParameterUpdate 61 | if _, err := cbor.Decode(cborBytes, &tmpUpdate); err != nil { 62 | t.Fatalf("unexpected error: %s", err) 63 | } 64 | tmpParams := testDef.startParams 65 | tmpParams.Update(&tmpUpdate) 66 | if !reflect.DeepEqual(tmpParams, testDef.expectedParams) { 67 | t.Fatalf( 68 | "did not get expected params:\n got: %#v\n wanted: %#v", 69 | tmpParams, 70 | testDef.expectedParams, 71 | ) 72 | } 73 | } 74 | } 75 | 76 | func TestMaryUtxorpc(t *testing.T) { 77 | inputParams := mary.MaryProtocolParameters{ 78 | MinFeeA: 500, 79 | MinFeeB: 2, 80 | MaxBlockBodySize: 65536, 81 | MaxTxSize: 16384, 82 | MaxBlockHeaderSize: 1024, 83 | KeyDeposit: 2000, 84 | PoolDeposit: 500000, 85 | MaxEpoch: 2160, 86 | NOpt: 100, 87 | A0: &cbor.Rat{Rat: big.NewRat(1, 2)}, 88 | Rho: &cbor.Rat{Rat: big.NewRat(3, 4)}, 89 | Tau: &cbor.Rat{Rat: big.NewRat(5, 6)}, 90 | ProtocolMajor: 8, 91 | ProtocolMinor: 0, 92 | MinUtxoValue: 1000000, 93 | } 94 | 95 | expectedUtxorpc := &cardano.PParams{ 96 | MinFeeCoefficient: 500, 97 | MinFeeConstant: 2, 98 | MaxBlockBodySize: 65536, 99 | MaxTxSize: 16384, 100 | MaxBlockHeaderSize: 1024, 101 | StakeKeyDeposit: 2000, 102 | PoolDeposit: 500000, 103 | PoolRetirementEpochBound: 2160, 104 | DesiredNumberOfPools: 100, 105 | PoolInfluence: &cardano.RationalNumber{ 106 | Numerator: int32(1), 107 | Denominator: uint32(2), 108 | }, 109 | MonetaryExpansion: &cardano.RationalNumber{ 110 | Numerator: int32(3), 111 | Denominator: uint32(4), 112 | }, 113 | TreasuryExpansion: &cardano.RationalNumber{ 114 | Numerator: int32(5), 115 | Denominator: uint32(6), 116 | }, 117 | ProtocolVersion: &cardano.ProtocolVersion{ 118 | Major: 8, 119 | Minor: 0, 120 | }, 121 | } 122 | 123 | result := inputParams.Utxorpc() 124 | 125 | if !reflect.DeepEqual(result, expectedUtxorpc) { 126 | t.Fatalf( 127 | "Utxorpc() test failed for Mary:\nExpected: %#v\nGot: %#v", 128 | expectedUtxorpc, 129 | result, 130 | ) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /ledger/shelley.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ledger 16 | 17 | import "github.com/blinklabs-io/gouroboros/ledger/shelley" 18 | 19 | // The below are compatibility types, constants, and functions for the Shelley era 20 | // to keep existing code working after a refactor of the ledger package 21 | 22 | // Shelley types 23 | type ( 24 | ShelleyBlock = shelley.ShelleyBlock 25 | ShelleyBlockHeader = shelley.ShelleyBlockHeader 26 | ShelleyTransaction = shelley.ShelleyTransaction 27 | ShelleyTransactionBody = shelley.ShelleyTransactionBody 28 | ShelleyTransactionInput = shelley.ShelleyTransactionInput 29 | ShelleyTransactionOutput = shelley.ShelleyTransactionOutput 30 | ShelleyTransactionWitnessSet = shelley.ShelleyTransactionWitnessSet 31 | ShelleyProtocolParameters = shelley.ShelleyProtocolParameters 32 | ShelleyProtocolParameterUpdate = shelley.ShelleyProtocolParameterUpdate 33 | ) 34 | 35 | // Shelley constants 36 | const ( 37 | EraIdShelley = shelley.EraIdShelley 38 | BlockTypeShelley = shelley.BlockTypeShelley 39 | BlockHeaderTypeShelley = shelley.BlockHeaderTypeShelley 40 | TxTypeShelley = shelley.TxTypeShelley 41 | ) 42 | 43 | // Shelley functions 44 | var ( 45 | NewShelleyBlockFromCbor = shelley.NewShelleyBlockFromCbor 46 | NewShelleyBlockHeaderFromCbor = shelley.NewShelleyBlockHeaderFromCbor 47 | NewShelleyTransactionInput = shelley.NewShelleyTransactionInput 48 | NewShelleyTransactionFromCbor = shelley.NewShelleyTransactionFromCbor 49 | NewShelleyTransactionBodyFromCbor = shelley.NewShelleyTransactionBodyFromCbor 50 | NewShelleyTransactionOutputFromCbor = shelley.NewShelleyTransactionOutputFromCbor 51 | ) 52 | -------------------------------------------------------------------------------- /ledger/shelley/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package shelley 16 | 17 | import ( 18 | "fmt" 19 | "strings" 20 | 21 | "github.com/blinklabs-io/gouroboros/ledger/common" 22 | ) 23 | 24 | type ExpiredUtxoError struct { 25 | Ttl uint64 26 | Slot uint64 27 | } 28 | 29 | func (e ExpiredUtxoError) Error() string { 30 | return fmt.Sprintf( 31 | "expired UTxO: TTL %d, slot %d", 32 | e.Ttl, 33 | e.Slot, 34 | ) 35 | } 36 | 37 | type InputSetEmptyUtxoError struct{} 38 | 39 | func (InputSetEmptyUtxoError) Error() string { 40 | return "input set empty" 41 | } 42 | 43 | type FeeTooSmallUtxoError struct { 44 | Provided uint64 45 | Min uint64 46 | } 47 | 48 | func (e FeeTooSmallUtxoError) Error() string { 49 | return fmt.Sprintf( 50 | "fee too small: provided %d, minimum %d", 51 | e.Provided, 52 | e.Min, 53 | ) 54 | } 55 | 56 | type BadInputsUtxoError struct { 57 | Inputs []common.TransactionInput 58 | } 59 | 60 | func (e BadInputsUtxoError) Error() string { 61 | tmpInputs := make([]string, len(e.Inputs)) 62 | for idx, tmpInput := range e.Inputs { 63 | tmpInputs[idx] = tmpInput.String() 64 | } 65 | return "bad input(s): " + strings.Join(tmpInputs, ", ") 66 | } 67 | 68 | type WrongNetworkError struct { 69 | NetId uint 70 | Addrs []common.Address 71 | } 72 | 73 | func (e WrongNetworkError) Error() string { 74 | tmpAddrs := make([]string, len(e.Addrs)) 75 | for idx, tmpAddr := range e.Addrs { 76 | tmpAddrs[idx] = tmpAddr.String() 77 | } 78 | return "wrong network: " + strings.Join(tmpAddrs, ", ") 79 | } 80 | 81 | type WrongNetworkWithdrawalError struct { 82 | NetId uint 83 | Addrs []common.Address 84 | } 85 | 86 | func (e WrongNetworkWithdrawalError) Error() string { 87 | tmpAddrs := make([]string, len(e.Addrs)) 88 | for idx, tmpAddr := range e.Addrs { 89 | tmpAddrs[idx] = tmpAddr.String() 90 | } 91 | return "wrong network withdrawals: " + strings.Join(tmpAddrs, ", ") 92 | } 93 | 94 | type ValueNotConservedUtxoError struct { 95 | Consumed uint64 96 | Produced uint64 97 | } 98 | 99 | func (e ValueNotConservedUtxoError) Error() string { 100 | return fmt.Sprintf( 101 | "value not conserved: consumed %d, produced %d", 102 | e.Consumed, 103 | e.Produced, 104 | ) 105 | } 106 | 107 | type OutputTooSmallUtxoError struct { 108 | Outputs []common.TransactionOutput 109 | } 110 | 111 | func (e OutputTooSmallUtxoError) Error() string { 112 | tmpOutputs := make([]string, len(e.Outputs)) 113 | for idx, tmpOutput := range e.Outputs { 114 | tmpOutputs[idx] = fmt.Sprintf("%#v", tmpOutput) 115 | } 116 | return "output too small: " + strings.Join(tmpOutputs, ", ") 117 | } 118 | 119 | type OutputBootAddrAttrsTooBigError struct { 120 | Outputs []common.TransactionOutput 121 | } 122 | 123 | func (e OutputBootAddrAttrsTooBigError) Error() string { 124 | tmpOutputs := make([]string, len(e.Outputs)) 125 | for idx, tmpOutput := range e.Outputs { 126 | tmpOutputs[idx] = fmt.Sprintf("%#v", tmpOutput) 127 | } 128 | return "output bootstrap address attributes too big: " + strings.Join( 129 | tmpOutputs, 130 | ", ", 131 | ) 132 | } 133 | 134 | type MaxTxSizeUtxoError struct { 135 | TxSize uint 136 | MaxTxSize uint 137 | } 138 | 139 | func (e MaxTxSizeUtxoError) Error() string { 140 | return fmt.Sprintf( 141 | "transaction size too large: size %d, max %d", 142 | e.TxSize, 143 | e.MaxTxSize, 144 | ) 145 | } 146 | -------------------------------------------------------------------------------- /ledger/tx_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ledger_test 16 | 17 | import ( 18 | "encoding/hex" 19 | "testing" 20 | 21 | "github.com/blinklabs-io/gouroboros/ledger" 22 | ) 23 | 24 | func TestDetermineTransactionType(t *testing.T) { 25 | testDefs := []struct { 26 | name string 27 | txCborHex string 28 | expectedTxType uint 29 | }{ 30 | { 31 | name: "ConwayTx", 32 | txCborHex: "84a500d9010281825820279184037d249e397d97293738370756da559718fcdefae9924834840046b37b01018282583900923d4b64e1d730a4baf3e6dc433a9686983940f458363f37aad7a1a9568b72f85522e4a17d44a45cd021b9741b55d7cbc635c911625b015e1a00a9867082583900923d4b64e1d730a4baf3e6dc433a9686983940f458363f37aad7a1a9568b72f85522e4a17d44a45cd021b9741b55d7cbc635c911625b015e1b00000001267d7b04021a0002938d031a04e304e70800a100d9010281825820b829480e5d5827d2e1bd7c89176a5ca125c30812e54be7dbdf5c47c835a17f3d5840b13a76e7f2b19cde216fcad55ceeeb489ebab3dcf63ef1539ac4f535dece00411ee55c9b8188ef04b4aa3c72586e4a0ec9b89949367d7270fdddad3b18731403f5f6", 33 | expectedTxType: 6, 34 | }, 35 | { 36 | name: "ByronTx", 37 | txCborHex: "839f8200d8185824825820a12a839c25a01fa5d118167db5acdbd9e38172ae8f00e5ac0a4997ef792a200700ff9f8282d818584283581c6c9982e7f2b6dcc5eaa880e8014568913c8868d9f0f86eb687b2633ca101581e581c010d876783fb2b4d0d17c86df29af8d35356ed3d1827bf4744f06700001a8dc672c11a000f4240ffa0", 38 | expectedTxType: 0, 39 | }, 40 | } 41 | for _, testDef := range testDefs { 42 | txCbor, err := hex.DecodeString(testDef.txCborHex) 43 | if err != nil { 44 | t.Fatalf("unexpected error: %s", err) 45 | } 46 | tmpTxType, err := ledger.DetermineTransactionType(txCbor) 47 | if err != nil { 48 | t.Fatalf( 49 | "DetermineTransactionType failed with an unexpected error: %s", 50 | err, 51 | ) 52 | } 53 | if tmpTxType != testDef.expectedTxType { 54 | t.Fatalf( 55 | "did not get expected TX type: got %d, wanted %d", 56 | tmpTxType, 57 | testDef.expectedTxType, 58 | ) 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /muxer/segment.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package muxer 16 | 17 | import ( 18 | "math" 19 | "time" 20 | ) 21 | 22 | // Maximum segment payload length 23 | const SegmentMaxPayloadLength = 65535 24 | 25 | // Bit mask used to signify a response in the protocol ID field 26 | const segmentProtocolIdResponseFlag = 0x8000 27 | 28 | // SegmentHeader represents the header bytes on a segment 29 | type SegmentHeader struct { 30 | Timestamp uint32 31 | ProtocolId uint16 32 | PayloadLength uint16 33 | } 34 | 35 | // Segment represents basic unit of data in the Ouroboros protocol. 36 | // 37 | // Each chunk of data exchanged by a particular mini-protocol is wrapped in a muxer segment. 38 | // A segment consists of 4 bytes containing a timestamp, 2 bytes indicating which protocol the 39 | // data is part of, 2 bytes indicating the size of the payload (up to 65535 bytes), and then 40 | // the actual payload 41 | type Segment struct { 42 | SegmentHeader 43 | Payload []byte 44 | } 45 | 46 | // NewSegment returns a new Segment given a protocol ID, payload bytes, and whether the segment 47 | // is a response 48 | func NewSegment(protocolId uint16, payload []byte, isResponse bool) *Segment { 49 | // time since unix epoch even as nanoseconds will not overflow soon 50 | // #nosec G115 51 | header := SegmentHeader{ 52 | Timestamp: uint32(time.Now().UnixNano() & 0xffffffff), 53 | ProtocolId: protocolId, 54 | } 55 | if isResponse { 56 | header.ProtocolId = header.ProtocolId + segmentProtocolIdResponseFlag 57 | } 58 | size := len(payload) 59 | if size > SegmentMaxPayloadLength || size > math.MaxUint16 { 60 | return nil 61 | } 62 | // payload size fits within length 63 | header.PayloadLength = uint16(size) 64 | segment := &Segment{ 65 | SegmentHeader: header, 66 | Payload: payload, 67 | } 68 | return segment 69 | } 70 | 71 | // IsRequest returns true if the segment is not a response 72 | func (s *SegmentHeader) IsRequest() bool { 73 | return (s.ProtocolId & segmentProtocolIdResponseFlag) == 0 74 | } 75 | 76 | // IsResponse returns true if the segment is a response 77 | func (s *SegmentHeader) IsResponse() bool { 78 | return (s.ProtocolId & segmentProtocolIdResponseFlag) > 0 79 | } 80 | 81 | // GetProtocolId returns the protocol ID of the segment 82 | func (s *SegmentHeader) GetProtocolId() uint16 { 83 | if s.ProtocolId >= segmentProtocolIdResponseFlag { 84 | return s.ProtocolId - segmentProtocolIdResponseFlag 85 | } 86 | return s.ProtocolId 87 | } 88 | -------------------------------------------------------------------------------- /networks.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ouroboros 16 | 17 | import "github.com/blinklabs-io/gouroboros/ledger/common" 18 | 19 | // Network definitions 20 | var ( 21 | NetworkMainnet = Network{ 22 | Id: common.AddressNetworkMainnet, 23 | Name: "mainnet", 24 | NetworkMagic: 764824073, 25 | BootstrapPeers: []NetworkBootstrapPeer{ 26 | { 27 | Address: "backbone.cardano.iog.io", 28 | Port: 3001, 29 | }, 30 | { 31 | Address: "backbone.mainnet.emurgornd.com", 32 | Port: 3001, 33 | }, 34 | { 35 | Address: "backbone.mainnet.cardanofoundation.org", 36 | Port: 3001, 37 | }, 38 | }, 39 | } 40 | NetworkPreprod = Network{ 41 | Id: common.AddressNetworkTestnet, 42 | Name: "preprod", 43 | NetworkMagic: 1, 44 | BootstrapPeers: []NetworkBootstrapPeer{ 45 | { 46 | Address: "preprod-node.play.dev.cardano.org", 47 | Port: 3001, 48 | }, 49 | }, 50 | } 51 | NetworkPreview = Network{ 52 | Id: common.AddressNetworkTestnet, 53 | Name: "preview", 54 | NetworkMagic: 2, 55 | BootstrapPeers: []NetworkBootstrapPeer{ 56 | { 57 | Address: "preview-node.play.dev.cardano.org", 58 | Port: 3001, 59 | }, 60 | }, 61 | } 62 | NetworkSancho = Network{ 63 | Id: common.AddressNetworkTestnet, 64 | Name: "sanchonet", 65 | NetworkMagic: 4, 66 | BootstrapPeers: []NetworkBootstrapPeer{ 67 | { 68 | Address: "sanchonet-node.play.dev.cardano.org", 69 | Port: 3001, 70 | }, 71 | }, 72 | } 73 | NetworkDevnet = Network{ 74 | Id: common.AddressNetworkTestnet, 75 | Name: "devnet", 76 | NetworkMagic: 42, 77 | } 78 | ) 79 | 80 | // List of valid networks for use in lookup functions 81 | var networks = []Network{ 82 | NetworkMainnet, 83 | NetworkPreprod, 84 | NetworkPreview, 85 | NetworkSancho, 86 | NetworkDevnet, 87 | } 88 | 89 | // NetworkByName returns a predefined network by name 90 | func NetworkByName(name string) (Network, bool) { 91 | for _, network := range networks { 92 | if network.Name == name { 93 | return network, true 94 | } 95 | } 96 | return Network{}, false 97 | } 98 | 99 | // NetworkById returns a predefined network by ID 100 | func NetworkById(id uint8) (Network, bool) { 101 | for _, network := range networks { 102 | if network.Id == id { 103 | return network, true 104 | } 105 | } 106 | return Network{}, false 107 | } 108 | 109 | // NetworkByNetworkMagic returns a predefined network by network magic 110 | func NetworkByNetworkMagic(networkMagic uint32) (Network, bool) { 111 | for _, network := range networks { 112 | if network.NetworkMagic == networkMagic { 113 | return network, true 114 | } 115 | } 116 | return Network{}, false 117 | } 118 | 119 | // Network represents a Cardano network 120 | type Network struct { 121 | Id uint8 // network ID used for addresses 122 | Name string 123 | NetworkMagic uint32 124 | BootstrapPeers []NetworkBootstrapPeer 125 | } 126 | 127 | type NetworkBootstrapPeer struct { 128 | Address string 129 | Port uint 130 | } 131 | 132 | func (n Network) String() string { 133 | return n.Name 134 | } 135 | -------------------------------------------------------------------------------- /protocol/blockfetch/messages.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package blockfetch 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/blinklabs-io/gouroboros/cbor" 21 | "github.com/blinklabs-io/gouroboros/protocol" 22 | "github.com/blinklabs-io/gouroboros/protocol/common" 23 | ) 24 | 25 | const ( 26 | MessageTypeRequestRange = 0 27 | MessageTypeClientDone = 1 28 | MessageTypeStartBatch = 2 29 | MessageTypeNoBlocks = 3 30 | MessageTypeBlock = 4 31 | MessageTypeBatchDone = 5 32 | ) 33 | 34 | func NewMsgFromCbor(msgType uint, data []byte) (protocol.Message, error) { 35 | var ret protocol.Message 36 | switch msgType { 37 | case MessageTypeRequestRange: 38 | ret = &MsgRequestRange{} 39 | case MessageTypeClientDone: 40 | ret = &MsgClientDone{} 41 | case MessageTypeStartBatch: 42 | ret = &MsgStartBatch{} 43 | case MessageTypeNoBlocks: 44 | ret = &MsgNoBlocks{} 45 | case MessageTypeBlock: 46 | ret = &MsgBlock{} 47 | case MessageTypeBatchDone: 48 | ret = &MsgBatchDone{} 49 | } 50 | if _, err := cbor.Decode(data, ret); err != nil { 51 | return nil, fmt.Errorf("%s: decode error: %w", ProtocolName, err) 52 | } 53 | if ret != nil { 54 | // Store the raw message CBOR 55 | ret.SetCbor(data) 56 | } 57 | return ret, nil 58 | } 59 | 60 | type MsgRequestRange struct { 61 | protocol.MessageBase 62 | Start common.Point 63 | End common.Point 64 | } 65 | 66 | func NewMsgRequestRange(start common.Point, end common.Point) *MsgRequestRange { 67 | m := &MsgRequestRange{ 68 | MessageBase: protocol.MessageBase{ 69 | MessageType: MessageTypeRequestRange, 70 | }, 71 | Start: start, 72 | End: end, 73 | } 74 | return m 75 | } 76 | 77 | type MsgClientDone struct { 78 | protocol.MessageBase 79 | } 80 | 81 | func NewMsgClientDone() *MsgClientDone { 82 | m := &MsgClientDone{ 83 | MessageBase: protocol.MessageBase{ 84 | MessageType: MessageTypeClientDone, 85 | }, 86 | } 87 | return m 88 | } 89 | 90 | type MsgStartBatch struct { 91 | protocol.MessageBase 92 | } 93 | 94 | func NewMsgStartBatch() *MsgStartBatch { 95 | m := &MsgStartBatch{ 96 | MessageBase: protocol.MessageBase{ 97 | MessageType: MessageTypeStartBatch, 98 | }, 99 | } 100 | return m 101 | } 102 | 103 | type MsgNoBlocks struct { 104 | protocol.MessageBase 105 | } 106 | 107 | func NewMsgNoBlocks() *MsgNoBlocks { 108 | m := &MsgNoBlocks{ 109 | MessageBase: protocol.MessageBase{ 110 | MessageType: MessageTypeNoBlocks, 111 | }, 112 | } 113 | return m 114 | } 115 | 116 | type MsgBlock struct { 117 | protocol.MessageBase 118 | WrappedBlock []byte 119 | } 120 | 121 | func NewMsgBlock(wrappedBlock []byte) *MsgBlock { 122 | m := &MsgBlock{ 123 | MessageBase: protocol.MessageBase{ 124 | MessageType: MessageTypeBlock, 125 | }, 126 | WrappedBlock: wrappedBlock, 127 | } 128 | return m 129 | } 130 | 131 | func (m MsgBlock) MarshalCBOR() ([]byte, error) { 132 | tmp := []any{ 133 | m.MessageType, 134 | cbor.Tag{ 135 | Number: cbor.CborTagCbor, 136 | Content: m.WrappedBlock, 137 | }, 138 | } 139 | return cbor.Encode(&tmp) 140 | } 141 | 142 | type MsgBatchDone struct { 143 | protocol.MessageBase 144 | } 145 | 146 | func NewMsgBatchDone() *MsgBatchDone { 147 | m := &MsgBatchDone{ 148 | MessageBase: protocol.MessageBase{ 149 | MessageType: MessageTypeBatchDone, 150 | }, 151 | } 152 | return m 153 | } 154 | 155 | type WrappedBlock struct { 156 | cbor.StructAsArray 157 | Type uint 158 | RawBlock cbor.RawMessage 159 | } 160 | -------------------------------------------------------------------------------- /protocol/blockfetch/messages_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package blockfetch 16 | 17 | import ( 18 | "encoding/hex" 19 | "reflect" 20 | "testing" 21 | 22 | "github.com/blinklabs-io/gouroboros/cbor" 23 | "github.com/blinklabs-io/gouroboros/protocol" 24 | ) 25 | 26 | type testDefinition struct { 27 | CborHex string 28 | Message protocol.Message 29 | MessageType uint 30 | } 31 | 32 | // TODO: implement tests for more messages (#871) 33 | var tests = []testDefinition{ 34 | { 35 | CborHex: "8105", 36 | Message: NewMsgBatchDone(), 37 | MessageType: MessageTypeBatchDone, 38 | }, 39 | } 40 | 41 | func TestDecode(t *testing.T) { 42 | for _, test := range tests { 43 | cborData, err := hex.DecodeString(test.CborHex) 44 | if err != nil { 45 | t.Fatalf("failed to decode CBOR hex: %s", err) 46 | } 47 | msg, err := NewMsgFromCbor(test.MessageType, cborData) 48 | if err != nil { 49 | t.Fatalf("failed to decode CBOR: %s", err) 50 | } 51 | // Set the raw CBOR so the comparison should succeed 52 | test.Message.SetCbor(cborData) 53 | if !reflect.DeepEqual(msg, test.Message) { 54 | t.Fatalf( 55 | "CBOR did not decode to expected message object\n got: %#v\n wanted: %#v", 56 | msg, 57 | test.Message, 58 | ) 59 | } 60 | } 61 | } 62 | 63 | func TestEncode(t *testing.T) { 64 | for _, test := range tests { 65 | cborData, err := cbor.Encode(test.Message) 66 | if err != nil { 67 | t.Fatalf("failed to encode message to CBOR: %s", err) 68 | } 69 | cborHex := hex.EncodeToString(cborData) 70 | if cborHex != test.CborHex { 71 | t.Fatalf( 72 | "message did not encode to expected CBOR\n got: %s\n wanted: %s", 73 | cborHex, 74 | test.CborHex, 75 | ) 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /protocol/chainsync/error.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package chainsync 16 | 17 | import "errors" 18 | 19 | var ErrIntersectNotFound = errors.New("chain intersection not found") 20 | 21 | // StopChainSync is used as a special return value from a RollForward or RollBackward handler function 22 | // to signify that the sync process should be stopped 23 | var ErrStopSyncProcess = errors.New("stop sync process") 24 | -------------------------------------------------------------------------------- /protocol/chainsync/server_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package chainsync 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestHandleRequestNext_Callback(t *testing.T) { 24 | called := false 25 | server := &Server{ 26 | config: &Config{ 27 | RequestNextFunc: func(ctx CallbackContext) error { 28 | called = true 29 | return nil 30 | }, 31 | }, 32 | callbackContext: CallbackContext{}, 33 | } 34 | 35 | err := server.handleRequestNext() 36 | 37 | assert.NoError(t, err, "expected no error") 38 | assert.True(t, called, "expected RequestNextFunc to be called") 39 | } 40 | 41 | func TestHandleRequestNext_NilCallback(t *testing.T) { 42 | server := &Server{ 43 | config: &Config{ 44 | RequestNextFunc: nil, 45 | }, 46 | callbackContext: CallbackContext{}, 47 | } 48 | 49 | err := server.handleRequestNext() 50 | expectedError := "received chain-sync RequestNext message but no callback function is defined" 51 | 52 | assert.Error(t, err, "expected an error due to nil callback") 53 | assert.EqualError(t, err, expectedError) 54 | } 55 | -------------------------------------------------------------------------------- /protocol/chainsync/testdata/byron_main_block_testnet_f38aa5e8cf0b47d1ffa8b2385aa2d43882282db2ffd5ac0e3dadec1a6f2ecf08.hex: -------------------------------------------------------------------------------- 1 | 83851A4170CB175820067E773E6FFD66EA06F7F1C967E18A1EE0916797F6A1C1ABDF410379EB8B1DBE84830058200E5751C026E543B2E8AB2EB06099DAA1D1E5DF47778F7787FAAB45CDF12FE3A85820AFC0DA64183BF2664F3D4EEC7238D524BA607FAEEAB24FC100EB861DBA69971B8300582025777ACA9E4A73D48FC73B4F961D345B06D4A6F349CB7916570D35537D53479F5820D36A2619A672494604E11BB447CBCF5231E9F2BA25C2169177EDC941BD50AD6C5820AFC0DA64183BF2664F3D4EEC7238D524BA607FAEEAB24FC100EB861DBA69971B58204E66280CD94D591072349BEC0A3090A53AA945562EFB6D08D56E53654B0E409884820019040A5840CB51D29AB94E50D9A144D4F426564CEC700DEE4D9E857AACF91D3B689374D81F742A452818CF2489C16DFC186F6E9C76B7DF40845B7C450785F02D8809767575810482028284005840CB51D29AB94E50D9A144D4F426564CEC700DEE4D9E857AACF91D3B689374D81F742A452818CF2489C16DFC186F6E9C76B7DF40845B7C450785F02D880976757558407EC249D890D0AAF9A81207960C163AE2D6AC5E715CA6B96D5860E50D9F2B2B2A1D568FAA87C9CC8BFD433A3224A96EC5D101B4B6E9DB008DB8857F49BAE294B25840A304BF45B44FBCCC78F54B9014A6B2D4354631EBFF235AEBB2E71A15BDD582BE3794384C1BA713B99EF05766E92B8F438B2FC5AF349F2BB16E85E3780AA84C07584017A846A92477D3468690D97E28A44811BE4F8E3FDE79E478E4DCC432B13370C669C124BE03015EF2B1F121B807FFE74B1A92A4247EA8A22F9EA30D5DF671CB068483000000826A63617264616E6F2D736C00A058204BA92AA320C60ACC9AD7B9A64F2EDA55C4D2EC28E604FAF186708B4F0C4E8EDF849FFF8300D9010280D90102809FFF82809FFF81A0 2 | -------------------------------------------------------------------------------- /protocol/chainsync/testdata/rollforward_ntc_byron_main_block_testnet_f38aa5e8cf0b47d1ffa8b2385aa2d43882282db2ffd5ac0e3dadec1a6f2ecf08.hex: -------------------------------------------------------------------------------- 1 | 8302d81859029d820183851a4170cb175820067e773e6ffd66ea06f7f1c967e18a1ee0916797f6a1c1abdf410379eb8b1dbe84830058200e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a85820afc0da64183bf2664f3d4eec7238d524ba607faeeab24fc100eb861dba69971b8300582025777aca9e4a73d48fc73b4f961d345b06d4a6f349cb7916570d35537d53479f5820d36a2619a672494604e11bb447cbcf5231e9f2ba25c2169177edc941bd50ad6c5820afc0da64183bf2664f3d4eec7238d524ba607faeeab24fc100eb861dba69971b58204e66280cd94d591072349bec0a3090a53aa945562efb6d08d56e53654b0e409884820019040a5840cb51d29ab94e50d9a144d4f426564cec700dee4d9e857aacf91d3b689374d81f742a452818cf2489c16dfc186f6e9c76b7df40845b7c450785f02d8809767575810482028284005840cb51d29ab94e50d9a144d4f426564cec700dee4d9e857aacf91d3b689374d81f742a452818cf2489c16dfc186f6e9c76b7df40845b7c450785f02d880976757558407ec249d890d0aaf9a81207960c163ae2d6ac5e715ca6b96d5860e50d9f2b2b2a1d568faa87c9cc8bfd433a3224a96ec5d101b4b6e9db008db8857f49bae294b25840a304bf45b44fbccc78f54b9014a6b2d4354631ebff235aebb2e71a15bdd582be3794384c1ba713b99ef05766e92b8f438b2fc5af349f2bb16e85e3780aa84c07584017a846a92477d3468690d97e28a44811be4f8e3fde79e478e4dcc432b13370c669c124be03015ef2b1f121b807ffe74b1a92a4247ea8a22f9ea30d5df671cb068483000000826a63617264616e6f2d736c00a058204ba92aa320c60acc9ad7b9a64f2eda55c4d2ec28e604faf186708b4f0c4e8edf849fff8300d9010280d90102809fff82809fff81a0828219bf9f58207c288e72bb8c10439308901f379c2821945ed58bd1058578e8376f959078b32119bb99 2 | -------------------------------------------------------------------------------- /protocol/chainsync/testdata/rollforward_ntc_shelley_block_testnet_02b1c561715da9e540411123a6135ee319b02f60b9a11a603d3305556c04329f.hex: -------------------------------------------------------------------------------- 1 | 8302d8185903f0820284828f1a00185ecd1a001863c058207e16781b40ebf8b6da18f7b5e8ade855d6738095ef2f1c58c77e88b6e45997a4582032a954b521c0b19514408965831ef6839637de7a1a6168bcf8455c504ba93b9c5820a7b41d9c81c6129d2e4576873086e206434424e203bf4b3c7bb092d6763524e682584074e791c4a55a68418953d17b5a3c31c2e15d5971eb372321a13a938151ec78cfc37aaa9bb66d778db687f9d1b286335f3aa76287cc34cd5aace6a3e21912e2b6585021cab43a4c292a12fa018d5620f05a040ab7f58d1abf035122049b410127a04c44fdcc5af9812f69b2ed709b8cf08eb7294c478971f810118257b7a2957f363d5b35e12f31389ac03df2ffb50cbebe09825840fe4d8c01858f45a7af363ac50025eeebba3f594c52ee9224db0fbaa0f889b419b74408d586c33f6be98cb2d6b5151beabc4cf826db3760974ac21fade8d8e8b75850fe3209f881ce5d3048ac358b96bf809e95b0156d156c267ddcfc6f34ec2ae75f04d536285874b2bffaee3fc5fcd630d42e3bceda39174664bf96406d03454f03a109dcaae5a54dd12bc82d97c66708020358201033376be025cb705fd8dd02eda11cc73975a062b5d14ffd74d6ff69e69a2ff758206330dd04a06d755d7ac32eb44f9aa5ee67c389efdc22b9846e6025f2bd4b277d000058407fd4c77bc9d55234116178fee307ab67fc6f7af6b3642a993b5bba7ec65b10d3967e7c204ec0bfa92dfa992071e36afcec1bb0044dd635e9b1c828901e8e610402005901c0b4d5b2d1d66c71c0137fc2c5a611badf03fbe5679c12680b42c932abd043507839a02d4c5e04069cf51f46b3284f1da6f567a36c1f1adef37f6cfa0ea7dfd406c98ce19cf50af501845b0260919b4b9b9ce074af6ac02a28da1037884f3301b3efb030d7e61abd90b2de66dccb48afd315c25381af9bf3f676fdf5405a6a557d33c07bc6be69bf414de79f69ad20e38980f4afb4df55581572ec6ee935383ef6fa813084d940049297373a2d4f5fc09e70735615b9266066c2b890afef7fc9dfd1198b7e1403c94bccb793435e6b6c24db51bbbfb4c986898a653b9095f89a49d00c624752bba3843a4284d964de5b1dbf6a9ad67d6b351c59f74aa1c016f3b85e8417bd6d55274442410d2004e5f98d28fc1dab88f40a34e1af0bad15610072adfdad2ef982e6e2d093bc2c3cb0747523197c83059458322ae08fb03363e361516da86c3234416db647a98193e4881b310f1a05a7400d5e4cfa589647ad7b9075765ef450fbc4121c32467aaf68cdd69cd0e6ec7197122cfc877d6dcfe1f152b0cd2b0490c9d1e56c64eadc70b2aafdc11999078ef815100433ebbf70df842c2d56e9149e4d7876dd2f18c232489a56651ab2d94c5fcd3dc8753478552ebabe8080a082821a0353e5a758202809888408dd6f499ecdc868e10f635fa550af3ebc3b5165c9dacc023d1f52c51a00352183 2 | -------------------------------------------------------------------------------- /protocol/chainsync/testdata/rollforward_ntn_byron_ebb_testnet_8f8602837f7c6f8b8867dd1cbc1842cf51a27eaed2c70ef48325d00f8efb320f.hex: -------------------------------------------------------------------------------- 1 | 830282008282001a0009e397d8185850851a4170cb17582096fceff972c2c06bd3bb5243c39215333be6d56aaf4823073dca31afe5038471582069bc32be98bb4567c0d60f571781e92c9f3100b119d03ff8886ad3f4e2a0eff78200810081a082821a035289e35820c89e652408ec269379751c8b2bf0137297bf9f5d0fb2e76e19acf63d783c3a661a003516f4 2 | -------------------------------------------------------------------------------- /protocol/chainsync/testdata/rollforward_ntn_shelley_block_testnet_02b1c561715da9e540411123a6135ee319b02f60b9a11a603d3305556c04329f.hex: -------------------------------------------------------------------------------- 1 | 83028201d8185903ea828f1a00185ecd1a001863c058207e16781b40ebf8b6da18f7b5e8ade855d6738095ef2f1c58c77e88b6e45997a4582032a954b521c0b19514408965831ef6839637de7a1a6168bcf8455c504ba93b9c5820a7b41d9c81c6129d2e4576873086e206434424e203bf4b3c7bb092d6763524e682584074e791c4a55a68418953d17b5a3c31c2e15d5971eb372321a13a938151ec78cfc37aaa9bb66d778db687f9d1b286335f3aa76287cc34cd5aace6a3e21912e2b6585021cab43a4c292a12fa018d5620f05a040ab7f58d1abf035122049b410127a04c44fdcc5af9812f69b2ed709b8cf08eb7294c478971f810118257b7a2957f363d5b35e12f31389ac03df2ffb50cbebe09825840fe4d8c01858f45a7af363ac50025eeebba3f594c52ee9224db0fbaa0f889b419b74408d586c33f6be98cb2d6b5151beabc4cf826db3760974ac21fade8d8e8b75850fe3209f881ce5d3048ac358b96bf809e95b0156d156c267ddcfc6f34ec2ae75f04d536285874b2bffaee3fc5fcd630d42e3bceda39174664bf96406d03454f03a109dcaae5a54dd12bc82d97c66708020358201033376be025cb705fd8dd02eda11cc73975a062b5d14ffd74d6ff69e69a2ff758206330dd04a06d755d7ac32eb44f9aa5ee67c389efdc22b9846e6025f2bd4b277d000058407fd4c77bc9d55234116178fee307ab67fc6f7af6b3642a993b5bba7ec65b10d3967e7c204ec0bfa92dfa992071e36afcec1bb0044dd635e9b1c828901e8e610402005901c0b4d5b2d1d66c71c0137fc2c5a611badf03fbe5679c12680b42c932abd043507839a02d4c5e04069cf51f46b3284f1da6f567a36c1f1adef37f6cfa0ea7dfd406c98ce19cf50af501845b0260919b4b9b9ce074af6ac02a28da1037884f3301b3efb030d7e61abd90b2de66dccb48afd315c25381af9bf3f676fdf5405a6a557d33c07bc6be69bf414de79f69ad20e38980f4afb4df55581572ec6ee935383ef6fa813084d940049297373a2d4f5fc09e70735615b9266066c2b890afef7fc9dfd1198b7e1403c94bccb793435e6b6c24db51bbbfb4c986898a653b9095f89a49d00c624752bba3843a4284d964de5b1dbf6a9ad67d6b351c59f74aa1c016f3b85e8417bd6d55274442410d2004e5f98d28fc1dab88f40a34e1af0bad15610072adfdad2ef982e6e2d093bc2c3cb0747523197c83059458322ae08fb03363e361516da86c3234416db647a98193e4881b310f1a05a7400d5e4cfa589647ad7b9075765ef450fbc4121c32467aaf68cdd69cd0e6ec7197122cfc877d6dcfe1f152b0cd2b0490c9d1e56c64eadc70b2aafdc11999078ef815100433ebbf70df842c2d56e9149e4d7876dd2f18c232489a56651ab2d94c5fcd3dc8753478552ebabe82821a0352fc405820ea90218c8606aad58b90c2ad51e37fc35ed6d4c40d8944df0bc60d22f1e6dd651a00351a6e 2 | -------------------------------------------------------------------------------- /protocol/chainsync/testdata/shelley_block_testnet_02b1c561715da9e540411123a6135ee319b02f60b9a11a603d3305556c04329f.hex: -------------------------------------------------------------------------------- 1 | 84828F1A00185ECD1A001863C058207E16781B40EBF8B6DA18F7B5E8ADE855D6738095EF2F1C58C77E88B6E45997A4582032A954B521C0B19514408965831EF6839637DE7A1A6168BCF8455C504BA93B9C5820A7B41D9C81C6129D2E4576873086E206434424E203BF4B3C7BB092D6763524E682584074E791C4A55A68418953D17B5A3C31C2E15D5971EB372321A13A938151EC78CFC37AAA9BB66D778DB687F9D1B286335F3AA76287CC34CD5AACE6A3E21912E2B6585021CAB43A4C292A12FA018D5620F05A040AB7F58D1ABF035122049B410127A04C44FDCC5AF9812F69B2ED709B8CF08EB7294C478971F810118257B7A2957F363D5B35E12F31389AC03DF2FFB50CBEBE09825840FE4D8C01858F45A7AF363AC50025EEEBBA3F594C52EE9224DB0FBAA0F889B419B74408D586C33F6BE98CB2D6B5151BEABC4CF826DB3760974AC21FADE8D8E8B75850FE3209F881CE5D3048AC358B96BF809E95B0156D156C267DDCFC6F34EC2AE75F04D536285874B2BFFAEE3FC5FCD630D42E3BCEDA39174664BF96406D03454F03A109DCAAE5A54DD12BC82D97C66708020358201033376BE025CB705FD8DD02EDA11CC73975A062B5D14FFD74D6FF69E69A2FF758206330DD04A06D755D7AC32EB44F9AA5EE67C389EFDC22B9846E6025F2BD4B277D000058407FD4C77BC9D55234116178FEE307AB67FC6F7AF6B3642A993B5BBA7EC65B10D3967E7C204EC0BFA92DFA992071E36AFCEC1BB0044DD635E9B1C828901E8E610402005901C0B4D5B2D1D66C71C0137FC2C5A611BADF03FBE5679C12680B42C932ABD043507839A02D4C5E04069CF51F46B3284F1DA6F567A36C1F1ADEF37F6CFA0EA7DFD406C98CE19CF50AF501845B0260919B4B9B9CE074AF6AC02A28DA1037884F3301B3EFB030D7E61ABD90B2DE66DCCB48AFD315C25381AF9BF3F676FDF5405A6A557D33C07BC6BE69BF414DE79F69AD20E38980F4AFB4DF55581572EC6EE935383EF6FA813084D940049297373A2D4F5FC09E70735615B9266066C2B890AFEF7FC9DFD1198B7E1403C94BCCB793435E6B6C24DB51BBBFB4C986898A653B9095F89A49D00C624752BBA3843A4284D964DE5B1DBF6A9AD67D6B351C59F74AA1C016F3B85E8417BD6D55274442410D2004E5F98D28FC1DAB88F40A34E1AF0BAD15610072ADFDAD2EF982E6E2D093BC2C3CB0747523197C83059458322AE08FB03363E361516DA86C3234416DB647A98193E4881B310F1A05A7400D5E4CFA589647AD7B9075765EF450FBC4121C32467AAF68CDD69CD0E6EC7197122CFC877D6DCFE1F152B0CD2B0490C9D1E56C64EADC70B2AAFDC11999078EF815100433EBBF70DF842C2D56E9149E4D7876DD2F18C232489A56651AB2D94C5FCD3DC8753478552EBABE8080A0 2 | -------------------------------------------------------------------------------- /protocol/common/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // The common package contains types used by multiple mini-protocols 16 | package common 17 | 18 | import ( 19 | "github.com/blinklabs-io/gouroboros/cbor" 20 | ) 21 | 22 | // The Point type represents a point on the blockchain. It consists of a slot number and block hash 23 | type Point struct { 24 | // Tells the CBOR decoder to convert to/from a struct and a CBOR array 25 | _ struct{} `cbor:",toarray"` 26 | Slot uint64 27 | Hash []byte 28 | } 29 | 30 | // NewPoint returns a Point object with the specified slot number and block hash 31 | func NewPoint(slot uint64, blockHash []byte) Point { 32 | return Point{ 33 | Slot: slot, 34 | Hash: blockHash, 35 | } 36 | } 37 | 38 | // NewPointOrigin returns an "empty" Point object which represents the origin of the blockchain 39 | func NewPointOrigin() Point { 40 | return Point{} 41 | } 42 | 43 | // UnmarshalCBOR is a helper function for decoding a Point object from CBOR. The object content can vary, 44 | // so we need to do some special handling when decoding. It is not intended to be called directly. 45 | func (p *Point) UnmarshalCBOR(data []byte) error { 46 | var tmp []any 47 | if _, err := cbor.Decode(data, &tmp); err != nil { 48 | return err 49 | } 50 | if len(tmp) > 0 { 51 | p.Slot = tmp[0].(uint64) 52 | p.Hash = tmp[1].([]byte) 53 | } 54 | return nil 55 | } 56 | 57 | // MarshalCBOR is a helper function for encoding a Point object to CBOR. The object content can vary, so we 58 | // need to do some special handling when encoding. It is not intended to be called directly. 59 | func (p *Point) MarshalCBOR() ([]byte, error) { 60 | var data []any 61 | if p.Slot == 0 && p.Hash == nil { 62 | // Return an empty list if values are zero 63 | data = make([]any, 0) 64 | } else { 65 | data = []any{p.Slot, p.Hash} 66 | } 67 | return cbor.Encode(data) 68 | } 69 | 70 | // Tip represents a Point combined with a block number 71 | type Tip struct { 72 | cbor.StructAsArray 73 | Point Point 74 | BlockNumber uint64 75 | } 76 | -------------------------------------------------------------------------------- /protocol/error.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package protocol 16 | 17 | import "errors" 18 | 19 | var ErrProtocolShuttingDown = errors.New("protocol is shutting down") 20 | -------------------------------------------------------------------------------- /protocol/handshake/handshake.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package handshake implements the Ouroboros handshake protocol 16 | package handshake 17 | 18 | import ( 19 | "time" 20 | 21 | "github.com/blinklabs-io/gouroboros/connection" 22 | "github.com/blinklabs-io/gouroboros/protocol" 23 | ) 24 | 25 | // Protocol identifiers 26 | const ( 27 | ProtocolName = "handshake" 28 | ProtocolId = 0 29 | ) 30 | 31 | var ( 32 | statePropose = protocol.NewState(1, "Propose") 33 | stateConfirm = protocol.NewState(2, "Confirm") 34 | stateDone = protocol.NewState(3, "Done") 35 | ) 36 | 37 | // Handshake protocol state machine 38 | var StateMap = protocol.StateMap{ 39 | statePropose: protocol.StateMapEntry{ 40 | Agency: protocol.AgencyClient, 41 | Transitions: []protocol.StateTransition{ 42 | { 43 | MsgType: MessageTypeProposeVersions, 44 | NewState: stateConfirm, 45 | }, 46 | }, 47 | }, 48 | stateConfirm: protocol.StateMapEntry{ 49 | Agency: protocol.AgencyServer, 50 | Transitions: []protocol.StateTransition{ 51 | { 52 | MsgType: MessageTypeAcceptVersion, 53 | NewState: stateDone, 54 | }, 55 | { 56 | MsgType: MessageTypeRefuse, 57 | NewState: stateDone, 58 | }, 59 | }, 60 | }, 61 | stateDone: protocol.StateMapEntry{ 62 | Agency: protocol.AgencyNone, 63 | }, 64 | } 65 | 66 | // Handshake is a wrapper object that holds the client and server instances 67 | type Handshake struct { 68 | Client *Client 69 | Server *Server 70 | } 71 | 72 | // Config is used to configure the Handshake protocol instance 73 | type Config struct { 74 | ProtocolVersionMap protocol.ProtocolVersionMap 75 | FinishedFunc FinishedFunc 76 | Timeout time.Duration 77 | } 78 | 79 | // Callback context 80 | type CallbackContext struct { 81 | ConnectionId connection.ConnectionId 82 | Client *Client 83 | Server *Server 84 | } 85 | 86 | // Callback function types 87 | type FinishedFunc func(CallbackContext, uint16, protocol.VersionData) error 88 | 89 | // New returns a new Handshake object 90 | func New(protoOptions protocol.ProtocolOptions, cfg *Config) *Handshake { 91 | h := &Handshake{ 92 | Client: NewClient(protoOptions, cfg), 93 | Server: NewServer(protoOptions, cfg), 94 | } 95 | return h 96 | } 97 | 98 | // HandshakeOptionFunc represents a function used to modify the Handshake protocol config 99 | type HandshakeOptionFunc func(*Config) 100 | 101 | // NewConfig returns a new Handshake config object with the provided options 102 | func NewConfig(options ...HandshakeOptionFunc) Config { 103 | c := Config{ 104 | Timeout: 5 * time.Second, 105 | } 106 | // Apply provided options functions 107 | for _, option := range options { 108 | option(&c) 109 | } 110 | return c 111 | } 112 | 113 | // WithProtocolVersionMap specifies the supported protocol versions 114 | func WithProtocolVersionMap( 115 | versionMap protocol.ProtocolVersionMap, 116 | ) HandshakeOptionFunc { 117 | return func(c *Config) { 118 | c.ProtocolVersionMap = versionMap 119 | } 120 | } 121 | 122 | // WithFinishedFunc specifies the Finished callback function 123 | func WithFinishedFunc(finishedFunc FinishedFunc) HandshakeOptionFunc { 124 | return func(c *Config) { 125 | c.FinishedFunc = finishedFunc 126 | } 127 | } 128 | 129 | // WithTimeout specifies the timeout for the handshake operation 130 | func WithTimeout(timeout time.Duration) HandshakeOptionFunc { 131 | return func(c *Config) { 132 | c.Timeout = timeout 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /protocol/handshake/messages.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package handshake 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/blinklabs-io/gouroboros/cbor" 21 | "github.com/blinklabs-io/gouroboros/protocol" 22 | ) 23 | 24 | // Message types 25 | const ( 26 | MessageTypeProposeVersions = 0 27 | MessageTypeAcceptVersion = 1 28 | MessageTypeRefuse = 2 29 | ) 30 | 31 | // Refusal reasons 32 | const ( 33 | RefuseReasonVersionMismatch uint64 = 0 34 | RefuseReasonDecodeError uint64 = 1 35 | RefuseReasonRefused uint64 = 2 36 | ) 37 | 38 | // NewMsgFromCbor parses a Handshake message from CBOR 39 | func NewMsgFromCbor(msgType uint, data []byte) (protocol.Message, error) { 40 | var ret protocol.Message 41 | switch msgType { 42 | case MessageTypeProposeVersions: 43 | ret = &MsgProposeVersions{} 44 | case MessageTypeAcceptVersion: 45 | ret = &MsgAcceptVersion{} 46 | case MessageTypeRefuse: 47 | ret = &MsgRefuse{} 48 | } 49 | if _, err := cbor.Decode(data, ret); err != nil { 50 | return nil, fmt.Errorf("%s: decode error: %w", ProtocolName, err) 51 | } 52 | if ret != nil { 53 | // Store the raw message CBOR 54 | ret.SetCbor(data) 55 | } 56 | return ret, nil 57 | } 58 | 59 | type MsgProposeVersions struct { 60 | protocol.MessageBase 61 | VersionMap map[uint16]cbor.RawMessage 62 | } 63 | 64 | func NewMsgProposeVersions( 65 | versionMap protocol.ProtocolVersionMap, 66 | ) *MsgProposeVersions { 67 | rawVersionMap := map[uint16]cbor.RawMessage{} 68 | for version, versionData := range versionMap { 69 | // This should never fail with our known VersionData types 70 | cborData, _ := cbor.Encode(&versionData) 71 | rawVersionMap[version] = cbor.RawMessage(cborData) 72 | } 73 | m := &MsgProposeVersions{ 74 | MessageBase: protocol.MessageBase{ 75 | MessageType: MessageTypeProposeVersions, 76 | }, 77 | VersionMap: rawVersionMap, 78 | } 79 | return m 80 | } 81 | 82 | type MsgAcceptVersion struct { 83 | protocol.MessageBase 84 | Version uint16 85 | VersionData cbor.RawMessage 86 | } 87 | 88 | func NewMsgAcceptVersion( 89 | version uint16, 90 | versionData protocol.VersionData, 91 | ) *MsgAcceptVersion { 92 | // This should never fail with our known VersionData types 93 | cborData, _ := cbor.Encode(&versionData) 94 | m := &MsgAcceptVersion{ 95 | MessageBase: protocol.MessageBase{ 96 | MessageType: MessageTypeAcceptVersion, 97 | }, 98 | Version: version, 99 | VersionData: cbor.RawMessage(cborData), 100 | } 101 | return m 102 | } 103 | 104 | type MsgRefuse struct { 105 | protocol.MessageBase 106 | Reason []any 107 | } 108 | 109 | func NewMsgRefuse(reason []any) *MsgRefuse { 110 | m := &MsgRefuse{ 111 | MessageBase: protocol.MessageBase{ 112 | MessageType: MessageTypeRefuse, 113 | }, 114 | Reason: reason, 115 | } 116 | return m 117 | } 118 | -------------------------------------------------------------------------------- /protocol/handshake/messages_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package handshake 16 | 17 | import ( 18 | "encoding/hex" 19 | "reflect" 20 | "testing" 21 | 22 | "github.com/blinklabs-io/gouroboros/cbor" 23 | "github.com/blinklabs-io/gouroboros/protocol" 24 | ) 25 | 26 | type testDefinition struct { 27 | CborHex string 28 | Message protocol.Message 29 | MessageType uint 30 | } 31 | 32 | var tests = []testDefinition{ 33 | { 34 | CborHex: "8200a4078202f4088202f4098202f40a8202f4", 35 | MessageType: MessageTypeProposeVersions, 36 | Message: NewMsgProposeVersions( 37 | map[uint16]protocol.VersionData{ 38 | 7: protocol.VersionDataNtN7to10{ 39 | CborNetworkMagic: 2, 40 | CborInitiatorAndResponderDiffusionMode: false, 41 | }, 42 | 8: protocol.VersionDataNtN7to10{ 43 | CborNetworkMagic: 2, 44 | CborInitiatorAndResponderDiffusionMode: false, 45 | }, 46 | 9: protocol.VersionDataNtN7to10{ 47 | CborNetworkMagic: 2, 48 | CborInitiatorAndResponderDiffusionMode: false, 49 | }, 50 | 10: protocol.VersionDataNtN7to10{ 51 | CborNetworkMagic: 2, 52 | CborInitiatorAndResponderDiffusionMode: false, 53 | }, 54 | }, 55 | ), 56 | }, 57 | { 58 | CborHex: "83010a8202f4", 59 | MessageType: MessageTypeAcceptVersion, 60 | Message: NewMsgAcceptVersion( 61 | 10, 62 | protocol.VersionDataNtN7to10{ 63 | CborNetworkMagic: 2, 64 | CborInitiatorAndResponderDiffusionMode: false, 65 | }, 66 | ), 67 | }, 68 | { 69 | CborHex: "82028200840708090a", 70 | MessageType: MessageTypeRefuse, 71 | Message: NewMsgRefuse( 72 | []any{ 73 | uint64(RefuseReasonVersionMismatch), 74 | []any{ 75 | uint64(7), 76 | uint64(8), 77 | uint64(9), 78 | uint64(10), 79 | }, 80 | }, 81 | ), 82 | }, 83 | // TODO: add more tests for other refusal types (#854) 84 | } 85 | 86 | func TestDecode(t *testing.T) { 87 | for _, test := range tests { 88 | cborData, err := hex.DecodeString(test.CborHex) 89 | if err != nil { 90 | t.Fatalf("failed to decode CBOR hex: %s", err) 91 | } 92 | msg, err := NewMsgFromCbor(test.MessageType, cborData) 93 | if err != nil { 94 | t.Fatalf("failed to decode CBOR: %s", err) 95 | } 96 | // Set the raw CBOR so the comparison should succeed 97 | test.Message.SetCbor(cborData) 98 | if !reflect.DeepEqual(msg, test.Message) { 99 | t.Fatalf( 100 | "CBOR did not decode to expected message object\n got: %#v\n wanted: %#v", 101 | msg, 102 | test.Message, 103 | ) 104 | } 105 | } 106 | } 107 | 108 | func TestEncode(t *testing.T) { 109 | for _, test := range tests { 110 | cborData, err := cbor.Encode(test.Message) 111 | if err != nil { 112 | t.Fatalf("failed to encode message to CBOR: %s", err) 113 | } 114 | cborHex := hex.EncodeToString(cborData) 115 | if cborHex != test.CborHex { 116 | t.Fatalf( 117 | "message did not encode to expected CBOR\n got: %s\n wanted: %s", 118 | cborHex, 119 | test.CborHex, 120 | ) 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /protocol/keepalive/messages.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package keepalive 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/blinklabs-io/gouroboros/cbor" 21 | "github.com/blinklabs-io/gouroboros/protocol" 22 | ) 23 | 24 | const ( 25 | MessageTypeKeepAlive = 0 26 | MessageTypeKeepAliveResponse = 1 27 | MessageTypeDone = 2 28 | ) 29 | 30 | func NewMsgFromCbor(msgType uint, data []byte) (protocol.Message, error) { 31 | var ret protocol.Message 32 | switch msgType { 33 | case MessageTypeKeepAlive: 34 | ret = &MsgKeepAlive{} 35 | case MessageTypeKeepAliveResponse: 36 | ret = &MsgKeepAliveResponse{} 37 | case MessageTypeDone: 38 | ret = &MsgDone{} 39 | } 40 | if _, err := cbor.Decode(data, ret); err != nil { 41 | return nil, fmt.Errorf("%s: decode error: %w", ProtocolName, err) 42 | } 43 | if ret != nil { 44 | // Store the raw message CBOR 45 | ret.SetCbor(data) 46 | } 47 | return ret, nil 48 | } 49 | 50 | type MsgKeepAlive struct { 51 | protocol.MessageBase 52 | Cookie uint16 53 | } 54 | 55 | func NewMsgKeepAlive(cookie uint16) *MsgKeepAlive { 56 | msg := &MsgKeepAlive{ 57 | MessageBase: protocol.MessageBase{ 58 | MessageType: MessageTypeKeepAlive, 59 | }, 60 | Cookie: cookie, 61 | } 62 | return msg 63 | } 64 | 65 | type MsgKeepAliveResponse struct { 66 | protocol.MessageBase 67 | Cookie uint16 68 | } 69 | 70 | func NewMsgKeepAliveResponse(cookie uint16) *MsgKeepAliveResponse { 71 | msg := &MsgKeepAliveResponse{ 72 | MessageBase: protocol.MessageBase{ 73 | MessageType: MessageTypeKeepAliveResponse, 74 | }, 75 | Cookie: cookie, 76 | } 77 | return msg 78 | } 79 | 80 | type MsgDone struct { 81 | protocol.MessageBase 82 | } 83 | 84 | func NewMsgDone() *MsgDone { 85 | m := &MsgDone{ 86 | MessageBase: protocol.MessageBase{ 87 | MessageType: MessageTypeDone, 88 | }, 89 | } 90 | return m 91 | } 92 | -------------------------------------------------------------------------------- /protocol/keepalive/messages_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package keepalive 16 | 17 | import ( 18 | "encoding/hex" 19 | "github.com/blinklabs-io/gouroboros/cbor" 20 | "github.com/blinklabs-io/gouroboros/protocol" 21 | "reflect" 22 | "testing" 23 | ) 24 | 25 | type testDefinition struct { 26 | CborHex string 27 | Message protocol.Message 28 | MessageType uint 29 | } 30 | 31 | var tests = []testDefinition{ 32 | { 33 | CborHex: "8200193039", 34 | Message: NewMsgKeepAlive(12345), 35 | MessageType: MessageTypeKeepAlive, 36 | }, 37 | { 38 | CborHex: "8201193039", 39 | Message: NewMsgKeepAliveResponse(12345), 40 | MessageType: MessageTypeKeepAliveResponse, 41 | }, 42 | { 43 | CborHex: "8102", 44 | Message: NewMsgDone(), 45 | MessageType: MessageTypeDone, 46 | }, 47 | } 48 | 49 | func TestDecode(t *testing.T) { 50 | for _, test := range tests { 51 | cborData, err := hex.DecodeString(test.CborHex) 52 | if err != nil { 53 | t.Fatalf("failed to decode CBOR hex: %s", err) 54 | } 55 | msg, err := NewMsgFromCbor(test.MessageType, cborData) 56 | if err != nil { 57 | t.Fatalf("failed to decode CBOR: %s", err) 58 | } 59 | // Set the raw CBOR so the comparison should succeed 60 | test.Message.SetCbor(cborData) 61 | if !reflect.DeepEqual(msg, test.Message) { 62 | t.Fatalf( 63 | "CBOR did not decode to expected message object\n got: %#v\n wanted: %#v", 64 | msg, 65 | test.Message, 66 | ) 67 | } 68 | } 69 | } 70 | 71 | func TestEncode(t *testing.T) { 72 | for _, test := range tests { 73 | cborData, err := cbor.Encode(test.Message) 74 | if err != nil { 75 | t.Fatalf("failed to encode message to CBOR: %s", err) 76 | } 77 | cborHex := hex.EncodeToString(cborData) 78 | if cborHex != test.CborHex { 79 | t.Fatalf( 80 | "message did not encode to expected CBOR\n got: %s\n wanted: %s", 81 | cborHex, 82 | test.CborHex, 83 | ) 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /protocol/keepalive/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package keepalive 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/blinklabs-io/gouroboros/protocol" 21 | ) 22 | 23 | type Server struct { 24 | *protocol.Protocol 25 | config *Config 26 | callbackContext CallbackContext 27 | } 28 | 29 | func NewServer(protoOptions protocol.ProtocolOptions, cfg *Config) *Server { 30 | s := &Server{ 31 | config: cfg, 32 | } 33 | s.callbackContext = CallbackContext{ 34 | Server: s, 35 | ConnectionId: protoOptions.ConnectionId, 36 | } 37 | protoConfig := protocol.ProtocolConfig{ 38 | Name: ProtocolName, 39 | ProtocolId: ProtocolId, 40 | Muxer: protoOptions.Muxer, 41 | Logger: protoOptions.Logger, 42 | ErrorChan: protoOptions.ErrorChan, 43 | Mode: protoOptions.Mode, 44 | Role: protocol.ProtocolRoleServer, 45 | MessageHandlerFunc: s.messageHandler, 46 | MessageFromCborFunc: NewMsgFromCbor, 47 | StateMap: StateMap, 48 | InitialState: StateClient, 49 | } 50 | s.Protocol = protocol.New(protoConfig) 51 | return s 52 | } 53 | 54 | func (s *Server) messageHandler(msg protocol.Message) error { 55 | var err error 56 | switch msg.Type() { 57 | case MessageTypeKeepAlive: 58 | err = s.handleKeepAlive(msg) 59 | case MessageTypeDone: 60 | err = s.handleDone() 61 | default: 62 | err = fmt.Errorf( 63 | "%s: received unexpected message type %d", 64 | ProtocolName, 65 | msg.Type(), 66 | ) 67 | } 68 | return err 69 | } 70 | 71 | func (s *Server) handleKeepAlive(msgGeneric protocol.Message) error { 72 | s.Protocol.Logger(). 73 | Debug("keep alive", 74 | "component", "network", 75 | "protocol", ProtocolName, 76 | "role", "server", 77 | "connection_id", s.callbackContext.ConnectionId.String(), 78 | ) 79 | msg := msgGeneric.(*MsgKeepAlive) 80 | if s.config != nil && s.config.KeepAliveFunc != nil { 81 | // Call the user callback function 82 | return s.config.KeepAliveFunc(s.callbackContext, msg.Cookie) 83 | } else { 84 | // Send the keep-alive response 85 | resp := NewMsgKeepAliveResponse(msg.Cookie) 86 | return s.SendMessage(resp) 87 | } 88 | } 89 | 90 | func (s *Server) handleDone() error { 91 | s.Protocol.Logger(). 92 | Debug("done", 93 | "component", "network", 94 | "protocol", ProtocolName, 95 | "role", "server", 96 | "connection_id", s.callbackContext.ConnectionId.String(), 97 | ) 98 | if s.config != nil && s.config.DoneFunc != nil { 99 | // Call the user callback function 100 | return s.config.DoneFunc(s.callbackContext) 101 | } 102 | return nil 103 | } 104 | -------------------------------------------------------------------------------- /protocol/localstatequery/error.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package localstatequery 16 | 17 | import "errors" 18 | 19 | // ErrAcquireFailurePointTooOld indicates a failure to acquire a point due to it being too old 20 | var ErrAcquireFailurePointTooOld = errors.New("acquire failure: point too old") 21 | 22 | // ErrAcquireFailurePointNotOnChain indicates a failure to acquire a point due to it not being present on the chain 23 | var ErrAcquireFailurePointNotOnChain = errors.New( 24 | "acquire failure: point not on chain", 25 | ) 26 | -------------------------------------------------------------------------------- /protocol/localtxmonitor/messages_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package localtxmonitor 16 | 17 | import ( 18 | "encoding/hex" 19 | "fmt" 20 | "github.com/blinklabs-io/gouroboros/cbor" 21 | "github.com/blinklabs-io/gouroboros/protocol" 22 | "reflect" 23 | "testing" 24 | ) 25 | 26 | type testDefinition struct { 27 | CborHex string 28 | Message protocol.Message 29 | MessageType uint 30 | } 31 | 32 | var tests = []testDefinition{ 33 | { 34 | CborHex: "8100", 35 | MessageType: MessageTypeDone, 36 | Message: NewMsgDone(), 37 | }, 38 | { 39 | CborHex: "8101", 40 | MessageType: MessageTypeAcquire, 41 | Message: NewMsgAcquire(), 42 | }, 43 | { 44 | CborHex: "82021904d2", 45 | MessageType: MessageTypeAcquired, 46 | Message: NewMsgAcquired(1234), 47 | }, 48 | { 49 | CborHex: "8103", 50 | Message: NewMsgRelease(), 51 | MessageType: MessageTypeRelease, 52 | }, 53 | { 54 | CborHex: "8105", 55 | MessageType: MessageTypeNextTx, 56 | Message: NewMsgNextTx(), 57 | }, 58 | { 59 | CborHex: "8106", 60 | MessageType: MessageTypeReplyNextTx, 61 | Message: NewMsgReplyNextTx(0, nil), 62 | }, 63 | { 64 | CborHex: fmt.Sprintf("82068205d81844%x", 0xDEADBEEF), 65 | MessageType: MessageTypeReplyNextTx, 66 | Message: NewMsgReplyNextTx(5, []byte{0xDE, 0xAD, 0xBE, 0xEF}), 67 | }, 68 | { 69 | CborHex: fmt.Sprintf("820744%x", 0xDEADBEEF), 70 | MessageType: MessageTypeHasTx, 71 | Message: NewMsgHasTx([]byte{0xDE, 0xAD, 0xBE, 0xEF}), 72 | }, 73 | { 74 | CborHex: "8208f5", 75 | MessageType: MessageTypeReplyHasTx, 76 | Message: NewMsgReplyHasTx(true), 77 | }, 78 | { 79 | CborHex: "8109", 80 | MessageType: MessageTypeGetSizes, 81 | Message: NewMsgGetSizes(), 82 | }, 83 | { 84 | // [10, [1234, 2345, 3456]] 85 | CborHex: "820a831904d2190929190d80", 86 | MessageType: MessageTypeReplyGetSizes, 87 | Message: NewMsgReplyGetSizes(1234, 2345, 3456), 88 | }, 89 | } 90 | 91 | func TestDecode(t *testing.T) { 92 | for _, test := range tests { 93 | cborData, err := hex.DecodeString(test.CborHex) 94 | if err != nil { 95 | t.Fatalf("failed to decode CBOR hex: %s", err) 96 | } 97 | msg, err := NewMsgFromCbor(test.MessageType, cborData) 98 | if err != nil { 99 | t.Fatalf("failed to decode CBOR: %s", err) 100 | } 101 | // Set the raw CBOR so the comparison should succeed 102 | test.Message.SetCbor(cborData) 103 | if !reflect.DeepEqual(msg, test.Message) { 104 | t.Fatalf( 105 | "CBOR did not decode to expected message object\n got: %#v\n wanted: %#v", 106 | msg, 107 | test.Message, 108 | ) 109 | } 110 | } 111 | } 112 | 113 | func TestEncode(t *testing.T) { 114 | for _, test := range tests { 115 | cborData, err := cbor.Encode(test.Message) 116 | if err != nil { 117 | t.Fatalf("failed to encode message to CBOR: %s", err) 118 | } 119 | cborHex := hex.EncodeToString(cborData) 120 | if cborHex != test.CborHex { 121 | t.Fatalf( 122 | "message did not encode to expected CBOR\n got: %s\n wanted: %s", 123 | cborHex, 124 | test.CborHex, 125 | ) 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /protocol/localtxsubmission/error.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package localtxsubmission 16 | 17 | import ( 18 | "fmt" 19 | ) 20 | 21 | // TransactionRejectedError represents an explicit transaction rejection 22 | type TransactionRejectedError struct { 23 | ReasonCbor []byte 24 | Reason error 25 | } 26 | 27 | func (e TransactionRejectedError) Error() string { 28 | if e.Reason != nil { 29 | return e.Reason.Error() 30 | } else { 31 | return fmt.Sprintf("transaction rejected: CBOR reason hex: %x", e.ReasonCbor) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /protocol/localtxsubmission/localtxsubmission.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package localtxsubmission implements the Ouroboros local-tx-submission protocol 16 | package localtxsubmission 17 | 18 | import ( 19 | "time" 20 | 21 | "github.com/blinklabs-io/gouroboros/connection" 22 | "github.com/blinklabs-io/gouroboros/protocol" 23 | ) 24 | 25 | // Protocol identifiers 26 | const ( 27 | ProtocolName = "local-tx-submission" 28 | ProtocolId uint16 = 6 29 | ) 30 | 31 | var ( 32 | stateIdle = protocol.NewState(1, "Idle") 33 | stateBusy = protocol.NewState(2, "Busy") 34 | stateDone = protocol.NewState(3, "Done") 35 | ) 36 | 37 | // LocalTxSubmission protocol state machine 38 | var StateMap = protocol.StateMap{ 39 | stateIdle: protocol.StateMapEntry{ 40 | Agency: protocol.AgencyClient, 41 | Transitions: []protocol.StateTransition{ 42 | { 43 | MsgType: MessageTypeSubmitTx, 44 | NewState: stateBusy, 45 | }, 46 | }, 47 | }, 48 | stateBusy: protocol.StateMapEntry{ 49 | Agency: protocol.AgencyServer, 50 | Transitions: []protocol.StateTransition{ 51 | { 52 | MsgType: MessageTypeAcceptTx, 53 | NewState: stateIdle, 54 | }, 55 | { 56 | MsgType: MessageTypeRejectTx, 57 | NewState: stateIdle, 58 | }, 59 | }, 60 | }, 61 | stateDone: protocol.StateMapEntry{ 62 | Agency: protocol.AgencyNone, 63 | }, 64 | } 65 | 66 | // LocalTxSubmission is a wrapper object that holds the client and server instances 67 | type LocalTxSubmission struct { 68 | Client *Client 69 | Server *Server 70 | } 71 | 72 | // Config is used to configure the LocalTxSubmission protocol instance 73 | type Config struct { 74 | SubmitTxFunc SubmitTxFunc 75 | Timeout time.Duration 76 | } 77 | 78 | // Callback context 79 | type CallbackContext struct { 80 | ConnectionId connection.ConnectionId 81 | Client *Client 82 | Server *Server 83 | } 84 | 85 | // Callback function types 86 | type SubmitTxFunc func(CallbackContext, MsgSubmitTxTransaction) error 87 | 88 | // New returns a new LocalTxSubmission object 89 | func New( 90 | protoOptions protocol.ProtocolOptions, 91 | cfg *Config, 92 | ) *LocalTxSubmission { 93 | l := &LocalTxSubmission{ 94 | Client: NewClient(protoOptions, cfg), 95 | Server: NewServer(protoOptions, cfg), 96 | } 97 | return l 98 | } 99 | 100 | // LocalTxSubmissionOptionFunc represents a function used to modify the LocalTxSubmission protocol config 101 | type LocalTxSubmissionOptionFunc func(*Config) 102 | 103 | // NewConfig returns a new LocalTxSubmission config object with the provided options 104 | func NewConfig(options ...LocalTxSubmissionOptionFunc) Config { 105 | c := Config{ 106 | Timeout: 30 * time.Second, 107 | } 108 | // Apply provided options functions 109 | for _, option := range options { 110 | option(&c) 111 | } 112 | return c 113 | } 114 | 115 | // WithSubmitTxFunc specifies the callback function when a TX is submitted when acting as a server 116 | func WithSubmitTxFunc(submitTxFunc SubmitTxFunc) LocalTxSubmissionOptionFunc { 117 | return func(c *Config) { 118 | c.SubmitTxFunc = submitTxFunc 119 | } 120 | } 121 | 122 | // WithTimeout specifies the timeout for a TX submit operation when acting as a client 123 | func WithTimeout(timeout time.Duration) LocalTxSubmissionOptionFunc { 124 | return func(c *Config) { 125 | c.Timeout = timeout 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /protocol/localtxsubmission/messages.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package localtxsubmission 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/blinklabs-io/gouroboros/cbor" 21 | "github.com/blinklabs-io/gouroboros/protocol" 22 | ) 23 | 24 | // Message types 25 | const ( 26 | MessageTypeSubmitTx = 0 27 | MessageTypeAcceptTx = 1 28 | MessageTypeRejectTx = 2 29 | MessageTypeDone = 3 30 | ) 31 | 32 | // NewMsgFromCbor parses a LocalTxSubmission message from CBOR 33 | func NewMsgFromCbor(msgType uint, data []byte) (protocol.Message, error) { 34 | var ret protocol.Message 35 | switch msgType { 36 | case MessageTypeSubmitTx: 37 | ret = &MsgSubmitTx{} 38 | case MessageTypeAcceptTx: 39 | ret = &MsgAcceptTx{} 40 | case MessageTypeRejectTx: 41 | ret = &MsgRejectTx{} 42 | case MessageTypeDone: 43 | ret = &MsgDone{} 44 | } 45 | if _, err := cbor.Decode(data, ret); err != nil { 46 | return nil, fmt.Errorf("%s: decode error: %w", ProtocolName, err) 47 | } 48 | if ret != nil { 49 | // Store the raw message CBOR 50 | ret.SetCbor(data) 51 | } 52 | return ret, nil 53 | } 54 | 55 | type MsgSubmitTx struct { 56 | protocol.MessageBase 57 | Transaction MsgSubmitTxTransaction 58 | } 59 | 60 | type MsgSubmitTxTransaction struct { 61 | // Tells the CBOR decoder to convert to/from a struct and a CBOR array 62 | _ struct{} `cbor:",toarray"` 63 | EraId uint16 64 | Raw cbor.Tag 65 | } 66 | 67 | func NewMsgSubmitTx(eraId uint16, tx []byte) *MsgSubmitTx { 68 | m := &MsgSubmitTx{ 69 | MessageBase: protocol.MessageBase{ 70 | MessageType: MessageTypeSubmitTx, 71 | }, 72 | Transaction: MsgSubmitTxTransaction{ 73 | EraId: eraId, 74 | Raw: cbor.Tag{ 75 | // Wrapped CBOR 76 | Number: 24, 77 | Content: tx, 78 | }, 79 | }, 80 | } 81 | return m 82 | } 83 | 84 | type MsgAcceptTx struct { 85 | protocol.MessageBase 86 | } 87 | 88 | func NewMsgAcceptTx() *MsgAcceptTx { 89 | m := &MsgAcceptTx{ 90 | MessageBase: protocol.MessageBase{ 91 | MessageType: MessageTypeAcceptTx, 92 | }, 93 | } 94 | return m 95 | } 96 | 97 | type MsgRejectTx struct { 98 | protocol.MessageBase 99 | // We use RawMessage here because the failure reason can be numerous different 100 | // structures, and we'll need to do further processing 101 | Reason cbor.RawMessage 102 | } 103 | 104 | func NewMsgRejectTx(reasonCbor []byte) *MsgRejectTx { 105 | m := &MsgRejectTx{ 106 | MessageBase: protocol.MessageBase{ 107 | MessageType: MessageTypeRejectTx, 108 | }, 109 | Reason: cbor.RawMessage(reasonCbor), 110 | } 111 | return m 112 | } 113 | 114 | type MsgDone struct { 115 | protocol.MessageBase 116 | } 117 | 118 | func NewMsgDone() *MsgDone { 119 | m := &MsgDone{ 120 | MessageBase: protocol.MessageBase{ 121 | MessageType: MessageTypeDone, 122 | }, 123 | } 124 | return m 125 | } 126 | -------------------------------------------------------------------------------- /protocol/localtxsubmission/messages_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package localtxsubmission 16 | 17 | import ( 18 | "encoding/hex" 19 | "fmt" 20 | "reflect" 21 | "strings" 22 | "testing" 23 | 24 | "github.com/blinklabs-io/gouroboros/cbor" 25 | "github.com/blinklabs-io/gouroboros/ledger" 26 | "github.com/blinklabs-io/gouroboros/protocol" 27 | ) 28 | 29 | type testDefinition struct { 30 | CborHex string 31 | Message protocol.Message 32 | MessageType uint 33 | } 34 | 35 | // Helper function to allow inline hex decoding without capturing the error 36 | func hexDecode(data string) []byte { 37 | // Strip off any leading/trailing whitespace in hex string 38 | data = strings.TrimSpace(data) 39 | decoded, err := hex.DecodeString(data) 40 | if err != nil { 41 | panic(fmt.Sprintf("error decoding hex: %s", err)) 42 | } 43 | return decoded 44 | } 45 | 46 | // Valid CBOR that serves as a placeholder for real TX content in the tests 47 | // [h'DEADBEEF'] 48 | var placeholderTx = hexDecode("8144DEADBEEF") 49 | 50 | // Valid CBOR that serves as a placeholder for TX rejection errors 51 | // [2, 4] 52 | var placeholderRejectError = hexDecode("820204") 53 | 54 | var tests = []testDefinition{ 55 | { 56 | CborHex: fmt.Sprintf("82008204d81846%x", placeholderTx), 57 | MessageType: MessageTypeSubmitTx, 58 | Message: NewMsgSubmitTx(ledger.TxTypeAlonzo, placeholderTx), 59 | }, 60 | { 61 | CborHex: "8101", 62 | Message: NewMsgAcceptTx(), 63 | MessageType: MessageTypeAcceptTx, 64 | }, 65 | { 66 | CborHex: fmt.Sprintf("8202%x", placeholderRejectError), 67 | MessageType: MessageTypeRejectTx, 68 | Message: NewMsgRejectTx(placeholderRejectError), 69 | }, 70 | { 71 | CborHex: "8103", 72 | Message: NewMsgDone(), 73 | MessageType: MessageTypeDone, 74 | }, 75 | } 76 | 77 | func TestDecode(t *testing.T) { 78 | for _, test := range tests { 79 | cborData, err := hex.DecodeString(test.CborHex) 80 | if err != nil { 81 | t.Fatalf("failed to decode CBOR hex: %s", err) 82 | } 83 | msg, err := NewMsgFromCbor(test.MessageType, cborData) 84 | if err != nil { 85 | t.Fatalf("failed to decode CBOR: %s", err) 86 | } 87 | // Set the raw CBOR so the comparison should succeed 88 | test.Message.SetCbor(cborData) 89 | if !reflect.DeepEqual(msg, test.Message) { 90 | t.Fatalf( 91 | "CBOR did not decode to expected message object\n got: %#v\n wanted: %#v", 92 | msg, 93 | test.Message, 94 | ) 95 | } 96 | } 97 | } 98 | 99 | func TestEncode(t *testing.T) { 100 | for _, test := range tests { 101 | cborData, err := cbor.Encode(test.Message) 102 | if err != nil { 103 | t.Fatalf("failed to encode message to CBOR: %s", err) 104 | } 105 | cborHex := hex.EncodeToString(cborData) 106 | if cborHex != test.CborHex { 107 | t.Fatalf( 108 | "message did not encode to expected CBOR\n got: %s\n wanted: %s", 109 | cborHex, 110 | test.CborHex, 111 | ) 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /protocol/localtxsubmission/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package localtxsubmission 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | 21 | "github.com/blinklabs-io/gouroboros/cbor" 22 | "github.com/blinklabs-io/gouroboros/protocol" 23 | ) 24 | 25 | // Server implements the LocalTxSubmission server 26 | type Server struct { 27 | *protocol.Protocol 28 | config *Config 29 | callbackContext CallbackContext 30 | } 31 | 32 | // NewServer returns a new Server object 33 | func NewServer(protoOptions protocol.ProtocolOptions, cfg *Config) *Server { 34 | s := &Server{ 35 | config: cfg, 36 | } 37 | s.callbackContext = CallbackContext{ 38 | Server: s, 39 | ConnectionId: protoOptions.ConnectionId, 40 | } 41 | protoConfig := protocol.ProtocolConfig{ 42 | Name: ProtocolName, 43 | ProtocolId: ProtocolId, 44 | Muxer: protoOptions.Muxer, 45 | Logger: protoOptions.Logger, 46 | ErrorChan: protoOptions.ErrorChan, 47 | Mode: protoOptions.Mode, 48 | Role: protocol.ProtocolRoleServer, 49 | MessageHandlerFunc: s.messageHandler, 50 | MessageFromCborFunc: NewMsgFromCbor, 51 | StateMap: StateMap, 52 | InitialState: stateIdle, 53 | } 54 | s.Protocol = protocol.New(protoConfig) 55 | return s 56 | } 57 | 58 | func (s *Server) messageHandler(msg protocol.Message) error { 59 | var err error 60 | switch msg.Type() { 61 | case MessageTypeSubmitTx: 62 | err = s.handleSubmitTx(msg) 63 | case MessageTypeDone: 64 | err = s.handleDone() 65 | default: 66 | err = fmt.Errorf( 67 | "%s: received unexpected message type %d", 68 | ProtocolName, 69 | msg.Type(), 70 | ) 71 | } 72 | return err 73 | } 74 | 75 | func (s *Server) handleSubmitTx(msg protocol.Message) error { 76 | s.Protocol.Logger(). 77 | Debug("submit tx", 78 | "component", "network", 79 | "protocol", ProtocolName, 80 | "role", "server", 81 | "connection_id", s.callbackContext.ConnectionId.String(), 82 | ) 83 | if s.config.SubmitTxFunc == nil { 84 | return errors.New( 85 | "received local-tx-submission SubmitTx message but no callback function is defined", 86 | ) 87 | } 88 | msgSubmitTx := msg.(*MsgSubmitTx) 89 | // Call the user callback function and send Accept/RejectTx based on result 90 | err := s.config.SubmitTxFunc(s.callbackContext, msgSubmitTx.Transaction) 91 | if err == nil { 92 | newMsg := NewMsgAcceptTx() 93 | if err := s.SendMessage(newMsg); err != nil { 94 | return err 95 | } 96 | } else { 97 | errCbor, err := cbor.Encode(err.Error()) 98 | if err != nil { 99 | return err 100 | } 101 | newMsg := NewMsgRejectTx(errCbor) 102 | if err := s.SendMessage(newMsg); err != nil { 103 | return err 104 | } 105 | } 106 | return nil 107 | } 108 | 109 | func (s *Server) handleDone() error { 110 | s.Protocol.Logger(). 111 | Debug("done", 112 | "component", "network", 113 | "protocol", ProtocolName, 114 | "role", "server", 115 | "connection_id", s.callbackContext.ConnectionId.String(), 116 | ) 117 | return nil 118 | } 119 | -------------------------------------------------------------------------------- /protocol/message.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package protocol 16 | 17 | // Message provides a common interface for message utility functions 18 | type Message interface { 19 | SetCbor([]byte) 20 | Cbor() []byte 21 | Type() uint8 22 | } 23 | 24 | // MessageBase is the minimum implementation for a mini-protocol message 25 | type MessageBase struct { 26 | // Tells the CBOR decoder to convert to/from a struct and a CBOR array 27 | _ struct{} `cbor:",toarray"` 28 | rawCbor []byte 29 | MessageType uint8 30 | } 31 | 32 | // SetCbor stores the original CBOR that was parsed 33 | func (m *MessageBase) SetCbor(data []byte) { 34 | if data == nil { 35 | m.rawCbor = nil 36 | return 37 | } 38 | m.rawCbor = make([]byte, len(data)) 39 | copy(m.rawCbor, data) 40 | } 41 | 42 | // Cbor returns the original CBOR that was parsed 43 | func (m *MessageBase) Cbor() []byte { 44 | return m.rawCbor 45 | } 46 | 47 | // Type returns the message type 48 | func (m *MessageBase) Type() uint8 { 49 | return m.MessageType 50 | } 51 | -------------------------------------------------------------------------------- /protocol/peersharing/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package peersharing 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/blinklabs-io/gouroboros/protocol" 21 | ) 22 | 23 | // Client implements the PeerSharing client 24 | type Client struct { 25 | *protocol.Protocol 26 | config *Config 27 | callbackContext CallbackContext 28 | sharePeersChan chan []PeerAddress 29 | } 30 | 31 | // NewClient returns a new PeerSharing client object 32 | func NewClient(protoOptions protocol.ProtocolOptions, cfg *Config) *Client { 33 | if cfg == nil { 34 | tmpCfg := NewConfig() 35 | cfg = &tmpCfg 36 | } 37 | c := &Client{ 38 | config: cfg, 39 | sharePeersChan: make(chan []PeerAddress), 40 | } 41 | c.callbackContext = CallbackContext{ 42 | Client: c, 43 | ConnectionId: protoOptions.ConnectionId, 44 | } 45 | // Update state map with timeout 46 | stateMap := StateMap.Copy() 47 | if entry, ok := stateMap[stateBusy]; ok { 48 | entry.Timeout = c.config.Timeout 49 | stateMap[stateBusy] = entry 50 | } 51 | // Configure underlying Protocol 52 | protoConfig := protocol.ProtocolConfig{ 53 | Name: ProtocolName, 54 | ProtocolId: ProtocolId, 55 | Muxer: protoOptions.Muxer, 56 | Logger: protoOptions.Logger, 57 | ErrorChan: protoOptions.ErrorChan, 58 | Mode: protoOptions.Mode, 59 | Role: protocol.ProtocolRoleClient, 60 | MessageHandlerFunc: c.messageHandler, 61 | MessageFromCborFunc: NewMsgFromCbor, 62 | StateMap: stateMap, 63 | InitialState: stateIdle, 64 | } 65 | c.Protocol = protocol.New(protoConfig) 66 | return c 67 | } 68 | 69 | func (c *Client) GetPeers(amount uint8) ([]PeerAddress, error) { 70 | c.Protocol.Logger(). 71 | Debug(fmt.Sprintf("calling GetPeers(amount: %d)", amount), 72 | "component", "network", 73 | "protocol", ProtocolName, 74 | "role", "client", 75 | "connection_id", c.callbackContext.ConnectionId.String(), 76 | ) 77 | msg := NewMsgShareRequest(amount) 78 | if err := c.SendMessage(msg); err != nil { 79 | return nil, err 80 | } 81 | peers, ok := <-c.sharePeersChan 82 | if !ok { 83 | return nil, protocol.ErrProtocolShuttingDown 84 | } 85 | return peers, nil 86 | } 87 | 88 | func (c *Client) messageHandler(msg protocol.Message) error { 89 | var err error 90 | switch msg.Type() { 91 | case MessageTypeSharePeers: 92 | err = c.handleSharePeers(msg) 93 | default: 94 | err = fmt.Errorf( 95 | "%s: received unexpected message type %d", 96 | ProtocolName, 97 | msg.Type(), 98 | ) 99 | } 100 | return err 101 | } 102 | 103 | func (c *Client) handleSharePeers(msg protocol.Message) error { 104 | c.Protocol.Logger(). 105 | Debug("share peers", 106 | "component", "network", 107 | "protocol", ProtocolName, 108 | "role", "client", 109 | "connection_id", c.callbackContext.ConnectionId.String(), 110 | ) 111 | msgSharePeers := msg.(*MsgSharePeers) 112 | c.sharePeersChan <- msgSharePeers.PeerAddresses 113 | return nil 114 | } 115 | -------------------------------------------------------------------------------- /protocol/peersharing/messages_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package peersharing 16 | 17 | import ( 18 | "encoding/hex" 19 | "reflect" 20 | "testing" 21 | 22 | "github.com/blinklabs-io/gouroboros/cbor" 23 | "github.com/blinklabs-io/gouroboros/protocol" 24 | ) 25 | 26 | type testDefinition struct { 27 | CborHex string 28 | Message protocol.Message 29 | MessageType uint 30 | } 31 | 32 | var tests = []testDefinition{ 33 | { 34 | CborHex: "820007", 35 | MessageType: MessageTypeShareRequest, 36 | Message: NewMsgShareRequest(7), 37 | }, 38 | /* 39 | { 40 | CborHex: "8201xxxx", 41 | MessageType: MessageTypeSharePeers, 42 | Message: NewMsgSharePeers( 43 | 44 | ), 45 | }, 46 | */ 47 | { 48 | CborHex: "8102", 49 | MessageType: MessageTypeDone, 50 | Message: NewMsgDone(), 51 | }, 52 | } 53 | 54 | func TestDecode(t *testing.T) { 55 | for _, test := range tests { 56 | cborData, err := hex.DecodeString(test.CborHex) 57 | if err != nil { 58 | t.Fatalf("failed to decode CBOR hex: %s", err) 59 | } 60 | msg, err := NewMsgFromCbor(test.MessageType, cborData) 61 | if err != nil { 62 | t.Fatalf("failed to decode CBOR: %s", err) 63 | } 64 | // Set the raw CBOR so the comparison should succeed 65 | test.Message.SetCbor(cborData) 66 | if !reflect.DeepEqual(msg, test.Message) { 67 | t.Fatalf( 68 | "CBOR did not decode to expected message object\n got: %#v\n wanted: %#v", 69 | msg, 70 | test.Message, 71 | ) 72 | } 73 | } 74 | } 75 | 76 | func TestEncode(t *testing.T) { 77 | for _, test := range tests { 78 | cborData, err := cbor.Encode(test.Message) 79 | if err != nil { 80 | t.Fatalf("failed to encode message to CBOR: %s", err) 81 | } 82 | cborHex := hex.EncodeToString(cborData) 83 | if cborHex != test.CborHex { 84 | t.Fatalf( 85 | "message did not encode to expected CBOR\n got: %s\n wanted: %s", 86 | cborHex, 87 | test.CborHex, 88 | ) 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /protocol/peersharing/peersharing.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package peersharing implements the Ouroboros PeerSharing protocol 16 | package peersharing 17 | 18 | import ( 19 | "time" 20 | 21 | "github.com/blinklabs-io/gouroboros/connection" 22 | "github.com/blinklabs-io/gouroboros/protocol" 23 | ) 24 | 25 | // Protocol identifiers 26 | const ( 27 | ProtocolName = "peer-sharing" 28 | ProtocolId = 10 29 | ) 30 | 31 | var ( 32 | stateIdle = protocol.NewState(1, "Idle") 33 | stateBusy = protocol.NewState(2, "Busy") 34 | stateDone = protocol.NewState(3, "Done") 35 | ) 36 | 37 | // PeerSharing protocol state machine 38 | var StateMap = protocol.StateMap{ 39 | stateIdle: protocol.StateMapEntry{ 40 | Agency: protocol.AgencyClient, 41 | Transitions: []protocol.StateTransition{ 42 | { 43 | MsgType: MessageTypeShareRequest, 44 | NewState: stateBusy, 45 | }, 46 | { 47 | MsgType: MessageTypeDone, 48 | NewState: stateDone, 49 | }, 50 | }, 51 | }, 52 | stateBusy: protocol.StateMapEntry{ 53 | Agency: protocol.AgencyServer, 54 | Transitions: []protocol.StateTransition{ 55 | { 56 | MsgType: MessageTypeSharePeers, 57 | NewState: stateIdle, 58 | }, 59 | }, 60 | }, 61 | stateDone: protocol.StateMapEntry{ 62 | Agency: protocol.AgencyNone, 63 | }, 64 | } 65 | 66 | // PeerSharing is a wrapper object that holds the client and server instances 67 | type PeerSharing struct { 68 | Client *Client 69 | Server *Server 70 | } 71 | 72 | // Config is used to configure the PeerSharing protocol instance 73 | type Config struct { 74 | ShareRequestFunc ShareRequestFunc 75 | Timeout time.Duration 76 | } 77 | 78 | // Callback context 79 | type CallbackContext struct { 80 | ConnectionId connection.ConnectionId 81 | Client *Client 82 | Server *Server 83 | } 84 | 85 | // Callback function types 86 | type ShareRequestFunc func(CallbackContext, int) ([]PeerAddress, error) 87 | 88 | // New returns a new PeerSharing object 89 | func New(protoOptions protocol.ProtocolOptions, cfg *Config) *PeerSharing { 90 | h := &PeerSharing{ 91 | Client: NewClient(protoOptions, cfg), 92 | Server: NewServer(protoOptions, cfg), 93 | } 94 | return h 95 | } 96 | 97 | // PeerSharingOptionFunc represents a function used to modify the PeerSharing protocol config 98 | type PeerSharingOptionFunc func(*Config) 99 | 100 | // NewConfig returns a new PeerSharing config object with the provided options 101 | func NewConfig(options ...PeerSharingOptionFunc) Config { 102 | c := Config{ 103 | Timeout: 5 * time.Second, 104 | } 105 | // Apply provided options functions 106 | for _, option := range options { 107 | option(&c) 108 | } 109 | return c 110 | } 111 | 112 | // WithShareRequestFunc specifies the ShareRequest callback function 113 | func WithShareRequestFunc( 114 | shareRequestFunc ShareRequestFunc, 115 | ) PeerSharingOptionFunc { 116 | return func(c *Config) { 117 | c.ShareRequestFunc = shareRequestFunc 118 | } 119 | } 120 | 121 | // WithTimeout specifies the timeout for the handshake operation 122 | func WithTimeout(timeout time.Duration) PeerSharingOptionFunc { 123 | return func(c *Config) { 124 | c.Timeout = timeout 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /protocol/peersharing/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package peersharing 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | 21 | "github.com/blinklabs-io/gouroboros/protocol" 22 | ) 23 | 24 | // Server implements the PeerSharing server 25 | type Server struct { 26 | *protocol.Protocol 27 | config *Config 28 | callbackContext CallbackContext 29 | protoOptions protocol.ProtocolOptions 30 | } 31 | 32 | // NewServer returns a new PeerSharing server object 33 | func NewServer(protoOptions protocol.ProtocolOptions, cfg *Config) *Server { 34 | s := &Server{ 35 | config: cfg, 36 | // Save this for re-use later 37 | protoOptions: protoOptions, 38 | } 39 | s.callbackContext = CallbackContext{ 40 | Server: s, 41 | ConnectionId: protoOptions.ConnectionId, 42 | } 43 | s.initProtocol() 44 | return s 45 | } 46 | 47 | func (s *Server) initProtocol() { 48 | protoConfig := protocol.ProtocolConfig{ 49 | Name: ProtocolName, 50 | ProtocolId: ProtocolId, 51 | Muxer: s.protoOptions.Muxer, 52 | Logger: s.protoOptions.Logger, 53 | ErrorChan: s.protoOptions.ErrorChan, 54 | Mode: s.protoOptions.Mode, 55 | Role: protocol.ProtocolRoleServer, 56 | MessageHandlerFunc: s.handleMessage, 57 | MessageFromCborFunc: NewMsgFromCbor, 58 | StateMap: StateMap, 59 | InitialState: stateIdle, 60 | } 61 | s.Protocol = protocol.New(protoConfig) 62 | } 63 | 64 | func (s *Server) handleMessage(msg protocol.Message) error { 65 | var err error 66 | switch msg.Type() { 67 | case MessageTypeShareRequest: 68 | err = s.handleShareRequest(msg) 69 | case MessageTypeDone: 70 | err = s.handleDone(msg) 71 | default: 72 | err = fmt.Errorf( 73 | "%s: received unexpected message type %d", 74 | ProtocolName, 75 | msg.Type(), 76 | ) 77 | } 78 | return err 79 | } 80 | 81 | func (s *Server) handleShareRequest(msg protocol.Message) error { 82 | s.Protocol.Logger(). 83 | Debug("share request", 84 | "component", "network", 85 | "protocol", ProtocolName, 86 | "role", "server", 87 | "connection_id", s.callbackContext.ConnectionId.String(), 88 | ) 89 | if s.config == nil || s.config.ShareRequestFunc == nil { 90 | return errors.New( 91 | "received peer-sharing ShareRequest message but no callback function is defined", 92 | ) 93 | } 94 | msgShareRequest := msg.(*MsgShareRequest) 95 | peers, err := s.config.ShareRequestFunc( 96 | s.callbackContext, 97 | int(msgShareRequest.Amount), 98 | ) 99 | if err != nil { 100 | return err 101 | } 102 | msgResp := NewMsgSharePeers(peers) 103 | if err := s.SendMessage(msgResp); err != nil { 104 | return err 105 | } 106 | return nil 107 | } 108 | 109 | func (s *Server) handleDone(msg protocol.Message) error { 110 | s.Protocol.Logger(). 111 | Debug("done", 112 | "component", "network", 113 | "protocol", ProtocolName, 114 | "role", "server", 115 | "connection_id", s.callbackContext.ConnectionId.String(), 116 | ) 117 | // Restart protocol 118 | s.Stop() 119 | s.initProtocol() 120 | s.Start() 121 | return nil 122 | } 123 | -------------------------------------------------------------------------------- /protocol/state.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package protocol 16 | 17 | import ( 18 | "maps" 19 | "time" 20 | ) 21 | 22 | // ProtocolStateAgency is an enum representing the possible protocol state agency values 23 | type ProtocolStateAgency uint 24 | 25 | const ( 26 | AgencyNone ProtocolStateAgency = 0 // Default (invalid) value 27 | AgencyClient ProtocolStateAgency = 1 // Client agency 28 | AgencyServer ProtocolStateAgency = 2 // Server agency 29 | ) 30 | 31 | // State represents protocol state with both a numeric ID and a string identifier 32 | type State struct { 33 | Id uint 34 | Name string 35 | } 36 | 37 | // NewState returns a new State object with the provided numeric ID and string identifier 38 | func NewState(id uint, name string) State { 39 | return State{ 40 | Id: id, 41 | Name: name, 42 | } 43 | } 44 | 45 | // String returns the state string identifier 46 | func (s State) String() string { 47 | return s.Name 48 | } 49 | 50 | // StateTransition represents a protocol state transition 51 | type StateTransition struct { 52 | MsgType uint8 53 | NewState State 54 | MatchFunc StateTransitionMatchFunc 55 | } 56 | 57 | // StateTransitionMatchFunc represents a function that will take a Message and return a bool 58 | // that indicates whether the message is a match for the state transition rule 59 | type StateTransitionMatchFunc func(any, Message) bool 60 | 61 | // StateMapEntry represents a protocol state, it's possible state transitions, and an optional timeout 62 | type StateMapEntry struct { 63 | Agency ProtocolStateAgency 64 | Transitions []StateTransition 65 | Timeout time.Duration 66 | } 67 | 68 | // StateMap represents the state machine definition for a mini-protocol 69 | type StateMap map[State]StateMapEntry 70 | 71 | // Copy returns a copy of the state map. This is mostly for convenience, 72 | // since we need to copy the state map in various places 73 | func (s StateMap) Copy() StateMap { 74 | ret := StateMap{} 75 | maps.Copy(ret, s) 76 | return ret 77 | } 78 | -------------------------------------------------------------------------------- /protocol/txsubmission/messages_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package txsubmission 16 | 17 | import ( 18 | "encoding/hex" 19 | "reflect" 20 | "testing" 21 | 22 | "github.com/blinklabs-io/gouroboros/cbor" 23 | "github.com/blinklabs-io/gouroboros/protocol" 24 | ) 25 | 26 | type testDefinition struct { 27 | CborHex string 28 | Message protocol.Message 29 | MessageType uint 30 | } 31 | 32 | // TODO: implement tests for more messages 33 | var tests = []testDefinition{ 34 | { 35 | CborHex: "8104", 36 | Message: NewMsgDone(), 37 | MessageType: MessageTypeDone, 38 | }, 39 | { 40 | CborHex: "8106", 41 | Message: NewMsgInit(), 42 | MessageType: MessageTypeInit, 43 | }, 44 | } 45 | 46 | func TestDecode(t *testing.T) { 47 | for _, test := range tests { 48 | cborData, err := hex.DecodeString(test.CborHex) 49 | if err != nil { 50 | t.Fatalf("failed to decode CBOR hex: %s", err) 51 | } 52 | msg, err := NewMsgFromCbor(test.MessageType, cborData) 53 | if err != nil { 54 | t.Fatalf("failed to decode CBOR: %s", err) 55 | } 56 | // Set the raw CBOR so the comparison should succeed 57 | test.Message.SetCbor(cborData) 58 | if !reflect.DeepEqual(msg, test.Message) { 59 | t.Fatalf( 60 | "CBOR did not decode to expected message object\n got: %#v\n wanted: %#v", 61 | msg, 62 | test.Message, 63 | ) 64 | } 65 | } 66 | } 67 | 68 | func TestEncode(t *testing.T) { 69 | for _, test := range tests { 70 | cborData, err := cbor.Encode(test.Message) 71 | if err != nil { 72 | t.Fatalf("failed to encode message to CBOR: %s", err) 73 | } 74 | cborHex := hex.EncodeToString(cborData) 75 | if cborHex != test.CborHex { 76 | t.Fatalf( 77 | "message did not encode to expected CBOR\n got: %s\n wanted: %s", 78 | cborHex, 79 | test.CborHex, 80 | ) 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /scripts/cbor_dump_formatter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # This script reformats the CBOR visual representation generated by cbor.me so 4 | # that it's actually readable 5 | 6 | import sys 7 | 8 | with open(sys.argv[1], 'r') as input_file: 9 | data = input_file.read() 10 | print('size = %d' % len(data)) 11 | indent = 0 12 | in_string = False 13 | newline = False 14 | ignore_whitespace = False 15 | for idx, c in enumerate(data): 16 | if ignore_whitespace: 17 | if c == ' ': 18 | continue 19 | else: 20 | ignore_whitespace = False 21 | if in_string: 22 | if c == "'": 23 | in_string = False 24 | else: 25 | if c == '[': 26 | indent = indent + 2 27 | newline = True 28 | elif c == '{': 29 | indent = indent + 2 30 | newline = True 31 | elif c == ']': 32 | indent = indent - 2 33 | elif c == '}': 34 | indent = indent - 2 35 | elif c == ',': 36 | newline = True 37 | elif c == "'": 38 | in_string = True 39 | print(str(c), end='') 40 | if newline: 41 | newline = False 42 | ignore_whitespace = True 43 | print() 44 | print(' ' * indent, end='') 45 | -------------------------------------------------------------------------------- /utils/debug.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Blink Labs Software 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package utils 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | ) 21 | 22 | // DumpCborStructure generates an indented string representing an arbitrary data structure for debugging purposes 23 | func DumpCborStructure(data any, prefix string) string { 24 | var ret bytes.Buffer 25 | switch v := data.(type) { 26 | case int, uint, int16, uint16, int32, uint32, int64, uint64: 27 | return fmt.Sprintf("%s0x%x (%d),\n", prefix, v, v) 28 | case []uint8: 29 | return fmt.Sprintf("%s (length %d),\n", prefix, len(v)) 30 | case []any: 31 | ret.WriteString(prefix + "[\n") 32 | newPrefix := prefix 33 | // Override original user-provided prefix 34 | // This assumes the original prefix won't start with a space 35 | if len(newPrefix) > 1 && newPrefix[0] != ' ' { 36 | newPrefix = "" 37 | } 38 | // Add 2 more spaces to the new prefix 39 | newPrefix = " " + newPrefix 40 | /* 41 | var lastOutput string 42 | var lastOutputCount uint32 43 | */ 44 | for _, val := range v { 45 | tmp := DumpCborStructure(val, newPrefix) 46 | /* 47 | if lastOutput == "" || lastOutput == tmp { 48 | lastOutputCount += 1 49 | if lastOutputCount == 5 { 50 | ret.WriteString(fmt.Sprintf("%s...\n", newPrefix)) 51 | continue 52 | } else if lastOutputCount > 5 { 53 | lastOutput = tmp 54 | continue 55 | } 56 | } 57 | lastOutput = tmp 58 | */ 59 | ret.WriteString(tmp) 60 | } 61 | ret.WriteString(prefix + "],\n") 62 | case map[any]any: 63 | ret.WriteString(prefix + "{\n") 64 | newPrefix := prefix 65 | // Override original user-provided prefix 66 | // This assumes the original prefix won't start with a space 67 | if len(newPrefix) > 1 && newPrefix[0] != ' ' { 68 | newPrefix = "" 69 | } 70 | // Add 2 more spaces to the new prefix 71 | newPrefix = " " + newPrefix 72 | for key, val := range v { 73 | ret.WriteString(fmt.Sprintf("%s%#v => %#v,\n", newPrefix, key, val)) 74 | } 75 | ret.WriteString(prefix + "}\n") 76 | default: 77 | return fmt.Sprintf("%s%#v,\n", prefix, v) 78 | } 79 | return ret.String() 80 | } 81 | --------------------------------------------------------------------------------