@blinklabs/dingo 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +5 -0
- package/.github/CODEOWNERS +5 -0
- package/.github/assets/dingo-ate-my-blockchain.png +0 -0
- package/.github/assets/dingo-illustration.png +0 -0
- package/.github/assets/dingo-logo-with-text-horizontal.png +0 -0
- package/.github/assets/dingo-logo-with-text.png +0 -0
- package/.github/dependabot.yml +19 -0
- package/.github/dingo-20241210.png +0 -0
- package/.github/dingo.md +56 -0
- package/.github/workflows/ci-docker.yml +36 -0
- package/.github/workflows/conventional-commits.yml +17 -0
- package/.github/workflows/go-test.yml +29 -0
- package/.github/workflows/golangci-lint.yml +23 -0
- package/.github/workflows/publish.yml +207 -0
- package/.golangci.yml +71 -0
- package/Dockerfile +25 -0
- package/LICENSE +201 -0
- package/Makefile +53 -0
- package/README.md +150 -0
- package/blockfetch.go +144 -0
- package/chain/chain.go +504 -0
- package/chain/chain_test.go +468 -0
- package/chain/errors.go +80 -0
- package/chain/event.go +33 -0
- package/chain/iter.go +64 -0
- package/chainsync/chainsync.go +97 -0
- package/chainsync.go +223 -0
- package/cmd/dingo/load.go +52 -0
- package/cmd/dingo/main.go +118 -0
- package/cmd/dingo/serve.go +49 -0
- package/config/cardano/node.go +192 -0
- package/config/cardano/node_test.go +85 -0
- package/config/cardano/preview/README.md +4 -0
- package/config/cardano/preview/alonzo-genesis.json +196 -0
- package/config/cardano/preview/byron-genesis.json +117 -0
- package/config/cardano/preview/config.json +114 -0
- package/config/cardano/preview/conway-genesis.json +297 -0
- package/config/cardano/preview/shelley-genesis.json +68 -0
- package/config.go +245 -0
- package/connmanager/connection_manager.go +105 -0
- package/connmanager/connection_manager_test.go +185 -0
- package/connmanager/event.go +37 -0
- package/connmanager/listener.go +140 -0
- package/connmanager/outbound.go +93 -0
- package/connmanager/socket.go +55 -0
- package/connmanager/unix.go +78 -0
- package/custom-p2p-topology.json +24 -0
- package/custom-p2p-topology.json.backup +24 -0
- package/custom-p2p-topology.json.mainnet +37 -0
- package/database/account.go +138 -0
- package/database/block.go +362 -0
- package/database/certs.go +53 -0
- package/database/commit_timestamp.go +77 -0
- package/database/database.go +118 -0
- package/database/database_test.go +62 -0
- package/database/drep.go +27 -0
- package/database/epoch.go +121 -0
- package/database/immutable/chunk.go +182 -0
- package/database/immutable/immutable.go +350 -0
- package/database/immutable/immutable_test.go +59 -0
- package/database/immutable/primary.go +106 -0
- package/database/immutable/secondary.go +103 -0
- package/database/immutable/testdata/08893.chunk +0 -0
- package/database/immutable/testdata/08893.primary +0 -0
- package/database/immutable/testdata/08893.secondary +0 -0
- package/database/immutable/testdata/08894.chunk +0 -0
- package/database/immutable/testdata/08894.primary +0 -0
- package/database/immutable/testdata/08894.secondary +0 -0
- package/database/immutable/testdata/README.md +4 -0
- package/database/plugin/blob/badger/commit_timestamp.go +50 -0
- package/database/plugin/blob/badger/database.go +152 -0
- package/database/plugin/blob/badger/logger.go +63 -0
- package/database/plugin/blob/badger/metrics.go +98 -0
- package/database/plugin/blob/blob.go +19 -0
- package/database/plugin/blob/store.go +40 -0
- package/database/plugin/log.go +27 -0
- package/database/plugin/metadata/metadata.go +19 -0
- package/database/plugin/metadata/sqlite/account.go +224 -0
- package/database/plugin/metadata/sqlite/certs.go +58 -0
- package/database/plugin/metadata/sqlite/commit_timestamp.go +68 -0
- package/database/plugin/metadata/sqlite/database.go +218 -0
- package/database/plugin/metadata/sqlite/epoch.go +120 -0
- package/database/plugin/metadata/sqlite/models/account.go +81 -0
- package/database/plugin/metadata/sqlite/models/auth_committee_hot.go +26 -0
- package/database/plugin/metadata/sqlite/models/deregistration_drep.go +26 -0
- package/database/plugin/metadata/sqlite/models/drep.go +27 -0
- package/database/plugin/metadata/sqlite/models/epoch.go +31 -0
- package/database/plugin/metadata/sqlite/models/models.go +45 -0
- package/database/plugin/metadata/sqlite/models/pool.go +97 -0
- package/database/plugin/metadata/sqlite/models/pparam_update.go +27 -0
- package/database/plugin/metadata/sqlite/models/pparams.go +27 -0
- package/database/plugin/metadata/sqlite/models/registration_drep.go +28 -0
- package/database/plugin/metadata/sqlite/models/resign_committee_cold.go +27 -0
- package/database/plugin/metadata/sqlite/models/stake_registration_delegation.go +27 -0
- package/database/plugin/metadata/sqlite/models/stake_vote_delegation.go +27 -0
- package/database/plugin/metadata/sqlite/models/stake_vote_registration_delegation.go +27 -0
- package/database/plugin/metadata/sqlite/models/tip.go +26 -0
- package/database/plugin/metadata/sqlite/models/update_drep.go +27 -0
- package/database/plugin/metadata/sqlite/models/utxo.go +30 -0
- package/database/plugin/metadata/sqlite/models/vote_delegation.go +26 -0
- package/database/plugin/metadata/sqlite/models/vote_registration_delegation.go +26 -0
- package/database/plugin/metadata/sqlite/pool.go +240 -0
- package/database/plugin/metadata/sqlite/pparams.go +110 -0
- package/database/plugin/metadata/sqlite/tip.go +83 -0
- package/database/plugin/metadata/sqlite/utxo.go +292 -0
- package/database/plugin/metadata/store.go +168 -0
- package/database/plugin/option.go +190 -0
- package/database/plugin/plugin.go +20 -0
- package/database/plugin/register.go +118 -0
- package/database/pparams.go +145 -0
- package/database/tip.go +45 -0
- package/database/txn.go +147 -0
- package/database/types/types.go +74 -0
- package/database/types/types_test.go +83 -0
- package/database/utxo.go +263 -0
- package/dist/artifacts.json +1 -0
- package/dist/checksums.txt +22 -0
- package/dist/config.yaml +253 -0
- package/dist/dingo-0.5.0-SNAPSHOT-d9431e4.tar.gz +0 -0
- package/dist/dingo-0.5.0-SNAPSHOT-d9431e4.tar.gz.sbom.json +1 -0
- package/dist/dingo_0.5.0-SNAPSHOT-d9431e4_darwin_arm64.tar.gz +0 -0
- package/dist/dingo_0.5.0-SNAPSHOT-d9431e4_darwin_arm64.tar.gz.sbom.json +1 -0
- package/dist/dingo_0.5.0-SNAPSHOT-d9431e4_darwin_x86_64.tar.gz +0 -0
- package/dist/dingo_0.5.0-SNAPSHOT-d9431e4_darwin_x86_64.tar.gz.sbom.json +1 -0
- package/dist/dingo_0.5.0-SNAPSHOT-d9431e4_linux_amd64.apk +0 -0
- package/dist/dingo_0.5.0-SNAPSHOT-d9431e4_linux_amd64.apk.sbom.json +1 -0
- package/dist/dingo_0.5.0-SNAPSHOT-d9431e4_linux_amd64.deb +0 -0
- package/dist/dingo_0.5.0-SNAPSHOT-d9431e4_linux_amd64.deb.sbom.json +1 -0
- package/dist/dingo_0.5.0-SNAPSHOT-d9431e4_linux_amd64.rpm +0 -0
- package/dist/dingo_0.5.0-SNAPSHOT-d9431e4_linux_amd64.rpm.sbom.json +1 -0
- package/dist/dingo_0.5.0-SNAPSHOT-d9431e4_linux_arm64.apk +0 -0
- package/dist/dingo_0.5.0-SNAPSHOT-d9431e4_linux_arm64.apk.sbom.json +1 -0
- package/dist/dingo_0.5.0-SNAPSHOT-d9431e4_linux_arm64.deb +0 -0
- package/dist/dingo_0.5.0-SNAPSHOT-d9431e4_linux_arm64.deb.sbom.json +1 -0
- package/dist/dingo_0.5.0-SNAPSHOT-d9431e4_linux_arm64.rpm +0 -0
- package/dist/dingo_0.5.0-SNAPSHOT-d9431e4_linux_arm64.rpm.sbom.json +1 -0
- package/dist/dingo_0.5.0-SNAPSHOT-d9431e4_linux_arm64.tar.gz +0 -0
- package/dist/dingo_0.5.0-SNAPSHOT-d9431e4_linux_arm64.tar.gz.sbom.json +1 -0
- package/dist/dingo_0.5.0-SNAPSHOT-d9431e4_linux_x86_64.tar.gz +0 -0
- package/dist/dingo_0.5.0-SNAPSHOT-d9431e4_linux_x86_64.tar.gz.sbom.json +1 -0
- package/dist/dingo_darwin_amd64_v1/dingo +0 -0
- package/dist/dingo_darwin_arm64_v8.0/dingo +0 -0
- package/dist/dingo_linux_amd64_v1/dingo +0 -0
- package/dist/dingo_linux_arm64_v8.0/dingo +0 -0
- package/dist/homebrew/dingo.rb +51 -0
- package/dist/metadata.json +1 -0
- package/event/event.go +141 -0
- package/event/event_test.go +115 -0
- package/event/metrics.go +44 -0
- package/go.mod +98 -0
- package/go.sum +358 -0
- package/internal/config/config.go +145 -0
- package/internal/config/config_test.go +118 -0
- package/internal/node/load.go +149 -0
- package/internal/node/node.go +176 -0
- package/internal/version/version.go +33 -0
- package/ledger/certs.go +113 -0
- package/ledger/chainsync.go +578 -0
- package/ledger/eras/allegra.go +154 -0
- package/ledger/eras/alonzo.go +156 -0
- package/ledger/eras/babbage.go +154 -0
- package/ledger/eras/byron.go +42 -0
- package/ledger/eras/conway.go +158 -0
- package/ledger/eras/eras.go +44 -0
- package/ledger/eras/mary.go +154 -0
- package/ledger/eras/shelley.go +164 -0
- package/ledger/error.go +19 -0
- package/ledger/event.go +50 -0
- package/ledger/metrics.go +53 -0
- package/ledger/queries.go +260 -0
- package/ledger/slot.go +127 -0
- package/ledger/slot_test.go +147 -0
- package/ledger/state.go +726 -0
- package/ledger/view.go +73 -0
- package/localstatequery.go +50 -0
- package/localtxmonitor.go +44 -0
- package/localtxsubmission.go +52 -0
- package/mempool/consumer.go +98 -0
- package/mempool/mempool.go +322 -0
- package/node.go +320 -0
- package/package.json +33 -0
- package/peergov/event.go +27 -0
- package/peergov/peer.go +67 -0
- package/peergov/peergov.go +290 -0
- package/peersharing.go +70 -0
- package/preview-local-topology.json +23 -0
- package/topology/topology.go +69 -0
- package/topology/topology_test.go +179 -0
- package/tracing.go +65 -0
- package/txsubmission.go +233 -0
- package/utxorpc/query.go +311 -0
- package/utxorpc/submit.go +395 -0
- package/utxorpc/sync.go +276 -0
- package/utxorpc/utxorpc.go +166 -0
- package/utxorpc/watch.go +310 -0
package/ledger/state.go
ADDED
|
@@ -0,0 +1,726 @@
|
|
|
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
|
+
"encoding/hex"
|
|
19
|
+
"errors"
|
|
20
|
+
"fmt"
|
|
21
|
+
"io"
|
|
22
|
+
"log/slog"
|
|
23
|
+
"slices"
|
|
24
|
+
"sync"
|
|
25
|
+
"time"
|
|
26
|
+
|
|
27
|
+
"github.com/blinklabs-io/dingo/chain"
|
|
28
|
+
"github.com/blinklabs-io/dingo/config/cardano"
|
|
29
|
+
"github.com/blinklabs-io/dingo/database"
|
|
30
|
+
"github.com/blinklabs-io/dingo/event"
|
|
31
|
+
"github.com/blinklabs-io/dingo/ledger/eras"
|
|
32
|
+
ouroboros "github.com/blinklabs-io/gouroboros"
|
|
33
|
+
"github.com/blinklabs-io/gouroboros/cbor"
|
|
34
|
+
"github.com/blinklabs-io/gouroboros/ledger"
|
|
35
|
+
lcommon "github.com/blinklabs-io/gouroboros/ledger/common"
|
|
36
|
+
ochainsync "github.com/blinklabs-io/gouroboros/protocol/chainsync"
|
|
37
|
+
ocommon "github.com/blinklabs-io/gouroboros/protocol/common"
|
|
38
|
+
"github.com/prometheus/client_golang/prometheus"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
const (
|
|
42
|
+
cleanupConsumedUtxosInterval = 5 * time.Minute
|
|
43
|
+
cleanupConsumedUtxosSlotWindow = 50000 // TODO: calculate this from params (#395)
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
type LedgerStateConfig struct {
|
|
47
|
+
Logger *slog.Logger
|
|
48
|
+
DataDir string
|
|
49
|
+
EventBus *event.EventBus
|
|
50
|
+
CardanoNodeConfig *cardano.CardanoNodeConfig
|
|
51
|
+
PromRegistry prometheus.Registerer
|
|
52
|
+
// Callback(s)
|
|
53
|
+
BlockfetchRequestRangeFunc BlockfetchRequestRangeFunc
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// BlockfetchRequestRangeFunc describes a callback function used to start a blockfetch request for
|
|
57
|
+
// a range of blocks
|
|
58
|
+
type BlockfetchRequestRangeFunc func(ouroboros.ConnectionId, ocommon.Point, ocommon.Point) error
|
|
59
|
+
|
|
60
|
+
type LedgerState struct {
|
|
61
|
+
sync.RWMutex
|
|
62
|
+
chainsyncMutex sync.Mutex
|
|
63
|
+
config LedgerStateConfig
|
|
64
|
+
db *database.Database
|
|
65
|
+
timerCleanupConsumedUtxos *time.Timer
|
|
66
|
+
currentPParams lcommon.ProtocolParameters
|
|
67
|
+
currentEpoch database.Epoch
|
|
68
|
+
epochCache []database.Epoch
|
|
69
|
+
currentEra eras.EraDesc
|
|
70
|
+
currentTip ochainsync.Tip
|
|
71
|
+
currentTipBlockNonce []byte
|
|
72
|
+
metrics stateMetrics
|
|
73
|
+
chainsyncBlockEvents []BlockfetchEvent
|
|
74
|
+
chainsyncBlockfetchBusyTime time.Time
|
|
75
|
+
chainsyncBlockfetchDoneChan chan struct{}
|
|
76
|
+
chainsyncBlockfetchMutex sync.Mutex
|
|
77
|
+
chainsyncBlockfetchWaiting bool
|
|
78
|
+
chain *chain.Chain
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
func NewLedgerState(cfg LedgerStateConfig) (*LedgerState, error) {
|
|
82
|
+
ls := &LedgerState{
|
|
83
|
+
config: cfg,
|
|
84
|
+
}
|
|
85
|
+
if cfg.Logger == nil {
|
|
86
|
+
// Create logger to throw away logs
|
|
87
|
+
// We do this so we don't have to add guards around every log operation
|
|
88
|
+
cfg.Logger = slog.New(slog.NewJSONHandler(io.Discard, nil))
|
|
89
|
+
}
|
|
90
|
+
// Init metrics
|
|
91
|
+
ls.metrics.init(ls.config.PromRegistry)
|
|
92
|
+
// Load database
|
|
93
|
+
needsRecovery := false
|
|
94
|
+
db, err := database.New(cfg.Logger, cfg.DataDir)
|
|
95
|
+
if db == nil {
|
|
96
|
+
ls.config.Logger.Error(
|
|
97
|
+
"failed to create database",
|
|
98
|
+
"error",
|
|
99
|
+
"empty database returned",
|
|
100
|
+
"component",
|
|
101
|
+
"ledger",
|
|
102
|
+
)
|
|
103
|
+
return nil, errors.New("empty database returned")
|
|
104
|
+
}
|
|
105
|
+
ls.db = db
|
|
106
|
+
if err != nil {
|
|
107
|
+
var dbErr database.CommitTimestampError
|
|
108
|
+
if !errors.As(err, &dbErr) {
|
|
109
|
+
return nil, err
|
|
110
|
+
}
|
|
111
|
+
ls.config.Logger.Warn(
|
|
112
|
+
"database initialization error, needs recovery",
|
|
113
|
+
"error",
|
|
114
|
+
err,
|
|
115
|
+
"component",
|
|
116
|
+
"ledger",
|
|
117
|
+
)
|
|
118
|
+
needsRecovery = true
|
|
119
|
+
}
|
|
120
|
+
// Load chain
|
|
121
|
+
chain, err := chain.NewChain(
|
|
122
|
+
ls.db,
|
|
123
|
+
ls.config.EventBus,
|
|
124
|
+
true, // persistent
|
|
125
|
+
)
|
|
126
|
+
if err != nil {
|
|
127
|
+
return nil, err
|
|
128
|
+
}
|
|
129
|
+
ls.chain = chain
|
|
130
|
+
// Run recovery if needed
|
|
131
|
+
if needsRecovery {
|
|
132
|
+
if err := ls.recoverCommitTimestampConflict(); err != nil {
|
|
133
|
+
return nil, fmt.Errorf("failed to recover database: %w", err)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Setup event handlers
|
|
137
|
+
ls.config.EventBus.SubscribeFunc(
|
|
138
|
+
ChainsyncEventType,
|
|
139
|
+
ls.handleEventChainsync,
|
|
140
|
+
)
|
|
141
|
+
ls.config.EventBus.SubscribeFunc(
|
|
142
|
+
BlockfetchEventType,
|
|
143
|
+
ls.handleEventBlockfetch,
|
|
144
|
+
)
|
|
145
|
+
// Schedule periodic process to purge consumed UTxOs outside of the rollback window
|
|
146
|
+
ls.scheduleCleanupConsumedUtxos()
|
|
147
|
+
// Load epoch info from DB
|
|
148
|
+
if err := ls.loadEpochs(nil); err != nil {
|
|
149
|
+
return nil, err
|
|
150
|
+
}
|
|
151
|
+
// Load current protocol parameters from DB
|
|
152
|
+
if err := ls.loadPParams(); err != nil {
|
|
153
|
+
return nil, err
|
|
154
|
+
}
|
|
155
|
+
// Load current tip
|
|
156
|
+
if err := ls.loadTip(); err != nil {
|
|
157
|
+
return nil, err
|
|
158
|
+
}
|
|
159
|
+
// Create genesis block
|
|
160
|
+
if err := ls.createGenesisBlock(); err != nil {
|
|
161
|
+
return nil, err
|
|
162
|
+
}
|
|
163
|
+
// Start goroutine to process new blocks
|
|
164
|
+
go ls.ledgerProcessBlocks()
|
|
165
|
+
return ls, nil
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
func (ls *LedgerState) recoverCommitTimestampConflict() error {
|
|
169
|
+
// Load current ledger tip
|
|
170
|
+
tmpTip, err := ls.db.GetTip(nil)
|
|
171
|
+
if err != nil {
|
|
172
|
+
return err
|
|
173
|
+
}
|
|
174
|
+
// Check if we can lookup tip block in chain
|
|
175
|
+
_, err = ls.chain.BlockByPoint(tmpTip.Point, nil)
|
|
176
|
+
if err != nil {
|
|
177
|
+
// Rollback to raw chain tip on error
|
|
178
|
+
chainTip := ls.chain.Tip()
|
|
179
|
+
if err = ls.rollback(chainTip.Point); err != nil {
|
|
180
|
+
return fmt.Errorf(
|
|
181
|
+
"failed to rollback ledger: %w",
|
|
182
|
+
err,
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return nil
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
func (ls *LedgerState) Chain() *chain.Chain {
|
|
190
|
+
return ls.chain
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
func (ls *LedgerState) Close() error {
|
|
194
|
+
return ls.db.Close()
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
func (ls *LedgerState) scheduleCleanupConsumedUtxos() {
|
|
198
|
+
ls.Lock()
|
|
199
|
+
defer ls.Unlock()
|
|
200
|
+
if ls.timerCleanupConsumedUtxos != nil {
|
|
201
|
+
ls.timerCleanupConsumedUtxos.Stop()
|
|
202
|
+
}
|
|
203
|
+
ls.timerCleanupConsumedUtxos = time.AfterFunc(
|
|
204
|
+
cleanupConsumedUtxosInterval,
|
|
205
|
+
func() {
|
|
206
|
+
defer func() {
|
|
207
|
+
// Schedule the next run
|
|
208
|
+
ls.scheduleCleanupConsumedUtxos()
|
|
209
|
+
}()
|
|
210
|
+
// Get the current tip, since we're querying by slot
|
|
211
|
+
tip := ls.Tip()
|
|
212
|
+
// Delete UTxOs that are marked as deleted and older than our slot window
|
|
213
|
+
ls.config.Logger.Debug(
|
|
214
|
+
"cleaning up consumed UTxOs",
|
|
215
|
+
"component", "ledger",
|
|
216
|
+
)
|
|
217
|
+
ls.Lock()
|
|
218
|
+
err := ls.db.UtxosDeleteConsumed(
|
|
219
|
+
tip.Point.Slot-cleanupConsumedUtxosSlotWindow,
|
|
220
|
+
nil,
|
|
221
|
+
)
|
|
222
|
+
ls.Unlock()
|
|
223
|
+
if err != nil {
|
|
224
|
+
ls.config.Logger.Error(
|
|
225
|
+
"failed to cleanup consumed UTxOs",
|
|
226
|
+
"component", "ledger",
|
|
227
|
+
"error", err,
|
|
228
|
+
)
|
|
229
|
+
return
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
func (ls *LedgerState) rollback(point ocommon.Point) error {
|
|
236
|
+
// Start a transaction
|
|
237
|
+
txn := ls.db.Transaction(true)
|
|
238
|
+
err := txn.Do(func(txn *database.Txn) error {
|
|
239
|
+
// Delete rolled-back UTxOs
|
|
240
|
+
err := ls.db.UtxosDeleteRolledback(point.Slot, txn)
|
|
241
|
+
if err != nil {
|
|
242
|
+
return fmt.Errorf("remove rolled-back UTxOs: %w", err)
|
|
243
|
+
}
|
|
244
|
+
// Restore spent UTxOs
|
|
245
|
+
err = ls.db.UtxosUnspend(point.Slot, txn)
|
|
246
|
+
if err != nil {
|
|
247
|
+
return fmt.Errorf(
|
|
248
|
+
"restore spent UTxOs after rollback: %w",
|
|
249
|
+
err,
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
// Update tip
|
|
253
|
+
ls.currentTip = ochainsync.Tip{
|
|
254
|
+
Point: point,
|
|
255
|
+
}
|
|
256
|
+
if point.Slot > 0 {
|
|
257
|
+
rollbackBlock, err := ls.chain.BlockByPoint(point, txn)
|
|
258
|
+
if err != nil {
|
|
259
|
+
return err
|
|
260
|
+
}
|
|
261
|
+
ls.currentTip.BlockNumber = rollbackBlock.Number
|
|
262
|
+
}
|
|
263
|
+
if err = ls.db.SetTip(ls.currentTip, txn); err != nil {
|
|
264
|
+
return err
|
|
265
|
+
}
|
|
266
|
+
ls.updateTipMetrics()
|
|
267
|
+
return nil
|
|
268
|
+
})
|
|
269
|
+
if err != nil {
|
|
270
|
+
return err
|
|
271
|
+
}
|
|
272
|
+
// Reload tip
|
|
273
|
+
if err := ls.loadTip(); err != nil {
|
|
274
|
+
return err
|
|
275
|
+
}
|
|
276
|
+
var hash string
|
|
277
|
+
if point.Slot == 0 {
|
|
278
|
+
hash = "<genesis>"
|
|
279
|
+
} else {
|
|
280
|
+
hash = hex.EncodeToString(point.Hash)
|
|
281
|
+
}
|
|
282
|
+
ls.config.Logger.Info(
|
|
283
|
+
fmt.Sprintf(
|
|
284
|
+
"chain rolled back, new tip: %s at slot %d",
|
|
285
|
+
hash,
|
|
286
|
+
point.Slot,
|
|
287
|
+
),
|
|
288
|
+
"component",
|
|
289
|
+
"ledger",
|
|
290
|
+
)
|
|
291
|
+
return nil
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
func (ls *LedgerState) transitionToEra(
|
|
295
|
+
txn *database.Txn,
|
|
296
|
+
nextEraId uint,
|
|
297
|
+
startEpoch uint64,
|
|
298
|
+
addedSlot uint64,
|
|
299
|
+
) error {
|
|
300
|
+
nextEra := eras.Eras[nextEraId]
|
|
301
|
+
if nextEra.HardForkFunc != nil {
|
|
302
|
+
// Perform hard fork
|
|
303
|
+
// This generally means upgrading pparams from previous era
|
|
304
|
+
newPParams, err := nextEra.HardForkFunc(
|
|
305
|
+
ls.config.CardanoNodeConfig,
|
|
306
|
+
ls.currentPParams,
|
|
307
|
+
)
|
|
308
|
+
if err != nil {
|
|
309
|
+
return err
|
|
310
|
+
}
|
|
311
|
+
ls.currentPParams = newPParams
|
|
312
|
+
ls.config.Logger.Debug(
|
|
313
|
+
"updated protocol params",
|
|
314
|
+
"pparams",
|
|
315
|
+
fmt.Sprintf("%#v", ls.currentPParams),
|
|
316
|
+
)
|
|
317
|
+
// Write pparams update to DB
|
|
318
|
+
pparamsCbor, err := cbor.Encode(&ls.currentPParams)
|
|
319
|
+
if err != nil {
|
|
320
|
+
return err
|
|
321
|
+
}
|
|
322
|
+
err = ls.db.SetPParams(
|
|
323
|
+
pparamsCbor,
|
|
324
|
+
addedSlot,
|
|
325
|
+
startEpoch,
|
|
326
|
+
nextEraId,
|
|
327
|
+
txn,
|
|
328
|
+
)
|
|
329
|
+
if err != nil {
|
|
330
|
+
return err
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
ls.currentEra = nextEra
|
|
334
|
+
return nil
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// consumeUtxo marks a UTxO as "deleted" without actually deleting it. This allows for a UTxO
|
|
338
|
+
// to be easily on rollback
|
|
339
|
+
func (ls *LedgerState) consumeUtxo(
|
|
340
|
+
txn *database.Txn,
|
|
341
|
+
utxoId ledger.TransactionInput,
|
|
342
|
+
slot uint64,
|
|
343
|
+
) error {
|
|
344
|
+
return ls.db.UtxoConsume(
|
|
345
|
+
utxoId,
|
|
346
|
+
slot,
|
|
347
|
+
txn,
|
|
348
|
+
)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
func (ls *LedgerState) ledgerProcessBlocks() {
|
|
352
|
+
iter, err := ls.chain.FromPoint(ls.currentTip.Point, false)
|
|
353
|
+
if err != nil {
|
|
354
|
+
ls.config.Logger.Error(
|
|
355
|
+
"failed to create chain iterator: " + err.Error(),
|
|
356
|
+
)
|
|
357
|
+
return
|
|
358
|
+
}
|
|
359
|
+
shouldBlock := false
|
|
360
|
+
// We chose 500 as an arbitrary max batch size. A "chain extended" message will be logged after each batch
|
|
361
|
+
nextBatch := make([]*chain.ChainIteratorResult, 0, 500)
|
|
362
|
+
var next, nextRollback *chain.ChainIteratorResult
|
|
363
|
+
var tmpBlock ledger.Block
|
|
364
|
+
var needsRollback bool
|
|
365
|
+
var end, i int
|
|
366
|
+
var txn *database.Txn
|
|
367
|
+
for {
|
|
368
|
+
// Gather up next batch of blocks
|
|
369
|
+
for {
|
|
370
|
+
next, err = iter.Next(shouldBlock)
|
|
371
|
+
shouldBlock = false
|
|
372
|
+
if err != nil {
|
|
373
|
+
if !errors.Is(err, chain.ErrIteratorChainTip) {
|
|
374
|
+
ls.config.Logger.Error(
|
|
375
|
+
"failed to get next block from chain iterator: " + err.Error(),
|
|
376
|
+
)
|
|
377
|
+
return
|
|
378
|
+
}
|
|
379
|
+
shouldBlock = true
|
|
380
|
+
// Break out of inner loop to flush DB transaction and log
|
|
381
|
+
break
|
|
382
|
+
}
|
|
383
|
+
if next == nil {
|
|
384
|
+
ls.config.Logger.Error("next block from chain iterator is nil")
|
|
385
|
+
return
|
|
386
|
+
}
|
|
387
|
+
nextBatch = append(nextBatch, next)
|
|
388
|
+
// End batch if there's a rollback, since we need special processing
|
|
389
|
+
if next.Rollback {
|
|
390
|
+
break
|
|
391
|
+
}
|
|
392
|
+
// Don't exceed our pre-allocated capacity
|
|
393
|
+
if len(nextBatch) == cap(nextBatch) {
|
|
394
|
+
break
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
// Process batch in groups of 50 to stay under DB txn limits
|
|
398
|
+
needsRollback = false
|
|
399
|
+
for i = 0; i < len(nextBatch); i += 50 {
|
|
400
|
+
ls.Lock()
|
|
401
|
+
end = min(
|
|
402
|
+
len(nextBatch),
|
|
403
|
+
i+50,
|
|
404
|
+
)
|
|
405
|
+
txn = ls.db.Transaction(true)
|
|
406
|
+
err = txn.Do(func(txn *database.Txn) error {
|
|
407
|
+
for _, next := range nextBatch[i:end] {
|
|
408
|
+
// Rollbacks need to be handled outside of the batch DB transaction
|
|
409
|
+
// A rollback should only occur at the end of a batch
|
|
410
|
+
if next.Rollback {
|
|
411
|
+
needsRollback = true
|
|
412
|
+
return nil
|
|
413
|
+
}
|
|
414
|
+
// Process block
|
|
415
|
+
tmpBlock, err = next.Block.Decode()
|
|
416
|
+
if err != nil {
|
|
417
|
+
return err
|
|
418
|
+
}
|
|
419
|
+
if err = ls.ledgerProcessBlock(txn, next.Point, tmpBlock); err != nil {
|
|
420
|
+
return err
|
|
421
|
+
}
|
|
422
|
+
// Update tip
|
|
423
|
+
ls.currentTip = ochainsync.Tip{
|
|
424
|
+
Point: next.Point,
|
|
425
|
+
BlockNumber: next.Block.Number,
|
|
426
|
+
}
|
|
427
|
+
// Update tip block nonce
|
|
428
|
+
ls.currentTipBlockNonce = next.Block.Nonce
|
|
429
|
+
}
|
|
430
|
+
// Update tip in database
|
|
431
|
+
if err := ls.db.SetTip(ls.currentTip, txn); err != nil {
|
|
432
|
+
return err
|
|
433
|
+
}
|
|
434
|
+
ls.updateTipMetrics()
|
|
435
|
+
return nil
|
|
436
|
+
})
|
|
437
|
+
if err != nil {
|
|
438
|
+
ls.Unlock()
|
|
439
|
+
ls.config.Logger.Error(
|
|
440
|
+
"failed to process block: " + err.Error(),
|
|
441
|
+
)
|
|
442
|
+
return
|
|
443
|
+
}
|
|
444
|
+
ls.Unlock()
|
|
445
|
+
}
|
|
446
|
+
// Process rollback from end of batch
|
|
447
|
+
if needsRollback {
|
|
448
|
+
needsRollback = false
|
|
449
|
+
// The rollback should be at the end of the batch
|
|
450
|
+
nextRollback = nextBatch[len(nextBatch)-1]
|
|
451
|
+
ls.Lock()
|
|
452
|
+
if err = ls.rollback(nextRollback.Point); err != nil {
|
|
453
|
+
ls.Unlock()
|
|
454
|
+
ls.config.Logger.Error(
|
|
455
|
+
"failed to process rollback: " + err.Error(),
|
|
456
|
+
)
|
|
457
|
+
return
|
|
458
|
+
}
|
|
459
|
+
ls.Unlock()
|
|
460
|
+
// Skip "chain extended" logging below if batch only contains a rollback
|
|
461
|
+
if len(nextBatch) == 1 {
|
|
462
|
+
// Clear out batch buffer
|
|
463
|
+
nextBatch = slices.Delete(nextBatch, 0, len(nextBatch))
|
|
464
|
+
continue
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if len(nextBatch) > 0 {
|
|
468
|
+
// Clear out batch buffer
|
|
469
|
+
nextBatch = slices.Delete(nextBatch, 0, len(nextBatch))
|
|
470
|
+
ls.config.Logger.Info(
|
|
471
|
+
fmt.Sprintf(
|
|
472
|
+
"chain extended, new tip: %x at slot %d",
|
|
473
|
+
ls.currentTip.Point.Hash,
|
|
474
|
+
ls.currentTip.Point.Slot,
|
|
475
|
+
),
|
|
476
|
+
"component",
|
|
477
|
+
"ledger",
|
|
478
|
+
)
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
func (ls *LedgerState) ledgerProcessBlock(
|
|
484
|
+
txn *database.Txn,
|
|
485
|
+
point ocommon.Point,
|
|
486
|
+
block ledger.Block,
|
|
487
|
+
) error {
|
|
488
|
+
// Check that we're processing things in order
|
|
489
|
+
if len(ls.currentTip.Point.Hash) > 0 {
|
|
490
|
+
if string(
|
|
491
|
+
block.PrevHash().Bytes(),
|
|
492
|
+
) != string(
|
|
493
|
+
ls.currentTip.Point.Hash,
|
|
494
|
+
) {
|
|
495
|
+
return fmt.Errorf(
|
|
496
|
+
"block %s (with prev hash %s) does not fit on current chain tip (%x)",
|
|
497
|
+
block.Hash().String(),
|
|
498
|
+
block.PrevHash().String(),
|
|
499
|
+
ls.currentTip.Point.Hash,
|
|
500
|
+
)
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
// TODO: track this using protocol params and hard forks
|
|
504
|
+
// Check for era change
|
|
505
|
+
if uint(block.Era().Id) != ls.currentEra.Id {
|
|
506
|
+
targetEraId := uint(block.Era().Id)
|
|
507
|
+
// Transition through every era between the current and the target era
|
|
508
|
+
for nextEraId := ls.currentEra.Id + 1; nextEraId <= targetEraId; nextEraId++ {
|
|
509
|
+
if err := ls.transitionToEra(txn, nextEraId, ls.currentEpoch.EpochId, point.Slot); err != nil {
|
|
510
|
+
return err
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
// Check for epoch rollover
|
|
515
|
+
if err := ls.processEpochRollover(txn, point); err != nil {
|
|
516
|
+
return err
|
|
517
|
+
}
|
|
518
|
+
// Process transactions
|
|
519
|
+
for _, tx := range block.Transactions() {
|
|
520
|
+
if err := ls.processTransaction(txn, tx, point); err != nil {
|
|
521
|
+
return err
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return nil
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
func (ls *LedgerState) updateTipMetrics() {
|
|
528
|
+
// Update metrics
|
|
529
|
+
ls.metrics.blockNum.Set(float64(ls.currentTip.BlockNumber))
|
|
530
|
+
ls.metrics.slotNum.Set(float64(ls.currentTip.Point.Slot))
|
|
531
|
+
ls.metrics.slotInEpoch.Set(
|
|
532
|
+
float64(ls.currentTip.Point.Slot - ls.currentEpoch.StartSlot),
|
|
533
|
+
)
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
func (ls *LedgerState) loadPParams() error {
|
|
537
|
+
pparams, err := ls.db.GetPParams(
|
|
538
|
+
ls.currentEpoch.EpochId,
|
|
539
|
+
ls.currentEra.DecodePParamsFunc,
|
|
540
|
+
nil,
|
|
541
|
+
)
|
|
542
|
+
if err != nil {
|
|
543
|
+
return err
|
|
544
|
+
}
|
|
545
|
+
ls.currentPParams = pparams
|
|
546
|
+
return nil
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
func (ls *LedgerState) loadEpochs(txn *database.Txn) error {
|
|
550
|
+
// Load and cache all epochs
|
|
551
|
+
epochs, err := ls.db.GetEpochs(txn)
|
|
552
|
+
if err != nil {
|
|
553
|
+
return err
|
|
554
|
+
}
|
|
555
|
+
ls.epochCache = epochs
|
|
556
|
+
// Set current epoch
|
|
557
|
+
if len(epochs) > 0 {
|
|
558
|
+
ls.currentEpoch = epochs[len(epochs)-1]
|
|
559
|
+
ls.currentEra = eras.Eras[ls.currentEpoch.EraId]
|
|
560
|
+
}
|
|
561
|
+
// Update metrics
|
|
562
|
+
ls.metrics.epochNum.Set(float64(ls.currentEpoch.EpochId))
|
|
563
|
+
return nil
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
func (ls *LedgerState) loadTip() error {
|
|
567
|
+
tmpTip, err := ls.db.GetTip(nil)
|
|
568
|
+
if err != nil {
|
|
569
|
+
return err
|
|
570
|
+
}
|
|
571
|
+
ls.currentTip = tmpTip
|
|
572
|
+
// Load tip block and set cached block nonce
|
|
573
|
+
if ls.currentTip.Point.Slot > 0 {
|
|
574
|
+
tipBlock, err := ls.chain.BlockByPoint(ls.currentTip.Point, nil)
|
|
575
|
+
if err != nil {
|
|
576
|
+
return err
|
|
577
|
+
}
|
|
578
|
+
ls.currentTipBlockNonce = tipBlock.Nonce
|
|
579
|
+
}
|
|
580
|
+
ls.updateTipMetrics()
|
|
581
|
+
return nil
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
func (ls *LedgerState) GetBlock(point ocommon.Point) (*database.Block, error) {
|
|
585
|
+
ret, err := ls.chain.BlockByPoint(point, nil)
|
|
586
|
+
if err != nil {
|
|
587
|
+
return nil, err
|
|
588
|
+
}
|
|
589
|
+
return &ret, nil
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// RecentChainPoints returns the requested count of recent chain points in descending order. This is used mostly
|
|
593
|
+
// for building a set of intersect points when acting as a chainsync client
|
|
594
|
+
func (ls *LedgerState) RecentChainPoints(count int) ([]ocommon.Point, error) {
|
|
595
|
+
tmpBlocks, err := database.BlocksRecent(ls.db, count)
|
|
596
|
+
if err != nil {
|
|
597
|
+
return nil, err
|
|
598
|
+
}
|
|
599
|
+
ret := []ocommon.Point{}
|
|
600
|
+
var tmpBlock database.Block
|
|
601
|
+
for _, tmpBlock = range tmpBlocks {
|
|
602
|
+
ret = append(
|
|
603
|
+
ret,
|
|
604
|
+
ocommon.NewPoint(tmpBlock.Slot, tmpBlock.Hash),
|
|
605
|
+
)
|
|
606
|
+
}
|
|
607
|
+
return ret, nil
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// GetIntersectPoint returns the intersect between the specified points and the current chain
|
|
611
|
+
func (ls *LedgerState) GetIntersectPoint(
|
|
612
|
+
points []ocommon.Point,
|
|
613
|
+
) (*ocommon.Point, error) {
|
|
614
|
+
tip := ls.Tip()
|
|
615
|
+
var ret ocommon.Point
|
|
616
|
+
var tmpBlock database.Block
|
|
617
|
+
var err error
|
|
618
|
+
foundOrigin := false
|
|
619
|
+
txn := ls.db.Transaction(false)
|
|
620
|
+
err = txn.Do(func(txn *database.Txn) error {
|
|
621
|
+
for _, point := range points {
|
|
622
|
+
// Ignore points with a slot later than our current tip
|
|
623
|
+
if point.Slot > tip.Point.Slot {
|
|
624
|
+
continue
|
|
625
|
+
}
|
|
626
|
+
// Ignore points with a slot earlier than an existing match
|
|
627
|
+
if point.Slot < ret.Slot {
|
|
628
|
+
continue
|
|
629
|
+
}
|
|
630
|
+
// Check for special origin point
|
|
631
|
+
if point.Slot == 0 && len(point.Hash) == 0 {
|
|
632
|
+
foundOrigin = true
|
|
633
|
+
continue
|
|
634
|
+
}
|
|
635
|
+
// Lookup block in metadata DB
|
|
636
|
+
tmpBlock, err = ls.chain.BlockByPoint(point, txn)
|
|
637
|
+
if err != nil {
|
|
638
|
+
if errors.Is(err, chain.ErrBlockNotFound) {
|
|
639
|
+
continue
|
|
640
|
+
}
|
|
641
|
+
return err
|
|
642
|
+
}
|
|
643
|
+
// Update return value
|
|
644
|
+
ret.Slot = tmpBlock.Slot
|
|
645
|
+
ret.Hash = tmpBlock.Hash
|
|
646
|
+
}
|
|
647
|
+
return nil
|
|
648
|
+
})
|
|
649
|
+
if err != nil {
|
|
650
|
+
return nil, err
|
|
651
|
+
}
|
|
652
|
+
if ret.Slot > 0 || foundOrigin {
|
|
653
|
+
return &ret, nil
|
|
654
|
+
}
|
|
655
|
+
return nil, nil
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// GetChainFromPoint returns a ChainIterator starting at the specified point. If inclusive is true, the iterator
|
|
659
|
+
// will start at the requested point, otherwise it will start at the next block.
|
|
660
|
+
func (ls *LedgerState) GetChainFromPoint(
|
|
661
|
+
point ocommon.Point,
|
|
662
|
+
inclusive bool,
|
|
663
|
+
) (*chain.ChainIterator, error) {
|
|
664
|
+
return ls.chain.FromPoint(point, inclusive)
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// Tip returns the current chain tip
|
|
668
|
+
func (ls *LedgerState) Tip() ochainsync.Tip {
|
|
669
|
+
return ls.currentTip
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// GetCurrentPParams returns the currentPParams value
|
|
673
|
+
func (ls *LedgerState) GetCurrentPParams() lcommon.ProtocolParameters {
|
|
674
|
+
return ls.currentPParams
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// UtxoByRef returns a single UTxO by reference
|
|
678
|
+
func (ls *LedgerState) UtxoByRef(
|
|
679
|
+
txId []byte,
|
|
680
|
+
outputIdx uint32,
|
|
681
|
+
) (database.Utxo, error) {
|
|
682
|
+
return ls.db.UtxoByRef(txId, outputIdx, nil)
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// UtxosByAddress returns all UTxOs that belong to the specified address
|
|
686
|
+
func (ls *LedgerState) UtxosByAddress(
|
|
687
|
+
addr ledger.Address,
|
|
688
|
+
) ([]database.Utxo, error) {
|
|
689
|
+
ret := []database.Utxo{}
|
|
690
|
+
utxos, err := ls.db.UtxosByAddress(addr, nil)
|
|
691
|
+
if err != nil {
|
|
692
|
+
return ret, err
|
|
693
|
+
}
|
|
694
|
+
var tmpUtxo database.Utxo
|
|
695
|
+
for _, utxo := range utxos {
|
|
696
|
+
tmpUtxo = database.Utxo(utxo)
|
|
697
|
+
ret = append(ret, tmpUtxo)
|
|
698
|
+
}
|
|
699
|
+
return ret, nil
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// ValidateTx runs ledger validation on the provided transaction
|
|
703
|
+
func (ls *LedgerState) ValidateTx(
|
|
704
|
+
tx lcommon.Transaction,
|
|
705
|
+
) error {
|
|
706
|
+
if ls.currentEra.ValidateTxFunc != nil {
|
|
707
|
+
txn := ls.db.Transaction(false)
|
|
708
|
+
err := txn.Do(func(txn *database.Txn) error {
|
|
709
|
+
lv := &LedgerView{
|
|
710
|
+
txn: txn,
|
|
711
|
+
ls: ls,
|
|
712
|
+
}
|
|
713
|
+
err := ls.currentEra.ValidateTxFunc(
|
|
714
|
+
tx,
|
|
715
|
+
ls.currentTip.Point.Slot,
|
|
716
|
+
lv,
|
|
717
|
+
ls.currentPParams,
|
|
718
|
+
)
|
|
719
|
+
return err
|
|
720
|
+
})
|
|
721
|
+
if err != nil {
|
|
722
|
+
return fmt.Errorf("TX %s failed validation: %w", tx.Hash(), err)
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
return nil
|
|
726
|
+
}
|