diff --git a/chain/chain.go b/chain/chain.go index a05ee1b..88f91f2 100644 --- a/chain/chain.go +++ b/chain/chain.go @@ -8,10 +8,8 @@ package chain import ( - "errors" - "fmt" - "forge.lthn.ai/core/go-blockchain/types" + coreerr "forge.lthn.ai/core/go-log" store "forge.lthn.ai/core/go-store" ) @@ -29,7 +27,7 @@ func New(s *store.Store) *Chain { func (c *Chain) Height() (uint64, error) { n, err := c.store.Count(groupBlocks) if err != nil { - return 0, fmt.Errorf("chain: height: %w", err) + return 0, coreerr.E("Chain.Height", "chain: height", err) } return uint64(n), nil } @@ -42,7 +40,7 @@ func (c *Chain) TopBlock() (*types.Block, *BlockMeta, error) { return nil, nil, err } if h == 0 { - return nil, nil, errors.New("chain: no blocks stored") + return nil, nil, coreerr.E("Chain.TopBlock", "chain: no blocks stored", nil) } return c.GetBlockByHeight(h - 1) } diff --git a/chain/index.go b/chain/index.go index 848cc7f..7ad6a2a 100644 --- a/chain/index.go +++ b/chain/index.go @@ -11,14 +11,16 @@ import ( "fmt" "strconv" - store "forge.lthn.ai/core/go-store" + coreerr "forge.lthn.ai/core/go-log" + "forge.lthn.ai/core/go-blockchain/types" + store "forge.lthn.ai/core/go-store" ) // MarkSpent records a key image as spent at the given block height. func (c *Chain) MarkSpent(ki types.KeyImage, height uint64) error { if err := c.store.Set(groupSpentKeys, ki.String(), strconv.FormatUint(height, 10)); err != nil { - return fmt.Errorf("chain: mark spent %s: %w", ki, err) + return coreerr.E("Chain.MarkSpent", fmt.Sprintf("chain: mark spent %s", ki), err) } return nil } @@ -30,7 +32,7 @@ func (c *Chain) IsSpent(ki types.KeyImage) (bool, error) { return false, nil } if err != nil { - return false, fmt.Errorf("chain: check spent %s: %w", ki, err) + return false, coreerr.E("Chain.IsSpent", fmt.Sprintf("chain: check spent %s", ki), err) } return true, nil } @@ -46,7 +48,7 @@ func (c *Chain) PutOutput(amount uint64, txID types.Hash, outNo uint32) (uint64, grp := outputGroup(amount) count, err := c.store.Count(grp) if err != nil { - return 0, fmt.Errorf("chain: output count: %w", err) + return 0, coreerr.E("Chain.PutOutput", "chain: output count", err) } gindex := uint64(count) @@ -56,12 +58,12 @@ func (c *Chain) PutOutput(amount uint64, txID types.Hash, outNo uint32) (uint64, } val, err := json.Marshal(entry) if err != nil { - return 0, fmt.Errorf("chain: marshal output: %w", err) + return 0, coreerr.E("Chain.PutOutput", "chain: marshal output", err) } key := strconv.FormatUint(gindex, 10) if err := c.store.Set(grp, key, string(val)); err != nil { - return 0, fmt.Errorf("chain: store output: %w", err) + return 0, coreerr.E("Chain.PutOutput", "chain: store output", err) } return gindex, nil } @@ -73,18 +75,18 @@ func (c *Chain) GetOutput(amount uint64, gindex uint64) (types.Hash, uint32, err val, err := c.store.Get(grp, key) if err != nil { if errors.Is(err, store.ErrNotFound) { - return types.Hash{}, 0, fmt.Errorf("chain: output %d:%d not found", amount, gindex) + return types.Hash{}, 0, coreerr.E("Chain.GetOutput", fmt.Sprintf("chain: output %d:%d not found", amount, gindex), nil) } - return types.Hash{}, 0, fmt.Errorf("chain: get output: %w", err) + return types.Hash{}, 0, coreerr.E("Chain.GetOutput", "chain: get output", err) } var entry outputEntry if err := json.Unmarshal([]byte(val), &entry); err != nil { - return types.Hash{}, 0, fmt.Errorf("chain: unmarshal output: %w", err) + return types.Hash{}, 0, coreerr.E("Chain.GetOutput", "chain: unmarshal output", err) } hash, err := types.HashFromHex(entry.TxID) if err != nil { - return types.Hash{}, 0, fmt.Errorf("chain: parse output tx_id: %w", err) + return types.Hash{}, 0, coreerr.E("Chain.GetOutput", "chain: parse output tx_id", err) } return hash, entry.OutNo, nil } @@ -93,7 +95,7 @@ func (c *Chain) GetOutput(amount uint64, gindex uint64) (types.Hash, uint32, err func (c *Chain) OutputCount(amount uint64) (uint64, error) { n, err := c.store.Count(outputGroup(amount)) if err != nil { - return 0, fmt.Errorf("chain: output count: %w", err) + return 0, coreerr.E("Chain.OutputCount", "chain: output count", err) } return uint64(n), nil } diff --git a/chain/levinconn.go b/chain/levinconn.go index c6b0e6d..749c352 100644 --- a/chain/levinconn.go +++ b/chain/levinconn.go @@ -6,9 +6,10 @@ package chain import ( - "fmt" "log" + coreerr "forge.lthn.ai/core/go-log" + "forge.lthn.ai/core/go-blockchain/p2p" levinpkg "forge.lthn.ai/core/go-p2p/node/levin" ) @@ -39,10 +40,10 @@ func (c *LevinP2PConn) handleMessage(hdr levinpkg.Header, data []byte) error { resp := p2p.TimedSyncRequest{PayloadData: c.localSync} payload, err := resp.Encode() if err != nil { - return fmt.Errorf("encode timed_sync response: %w", err) + return coreerr.E("LevinP2PConn.handleMessage", "encode timed_sync response", err) } if err := c.conn.WriteResponse(p2p.CommandTimedSync, payload, levinpkg.ReturnOK); err != nil { - return fmt.Errorf("write timed_sync response: %w", err) + return coreerr.E("LevinP2PConn.handleMessage", "write timed_sync response", err) } log.Printf("p2p: responded to timed_sync") return nil @@ -55,24 +56,24 @@ func (c *LevinP2PConn) RequestChain(blockIDs [][]byte) (uint64, [][]byte, error) req := p2p.RequestChain{BlockIDs: blockIDs} payload, err := req.Encode() if err != nil { - return 0, nil, fmt.Errorf("encode request_chain: %w", err) + return 0, nil, coreerr.E("LevinP2PConn.RequestChain", "encode request_chain", err) } // Send as notification (expectResponse=false) per CryptoNote protocol. if err := c.conn.WritePacket(p2p.CommandRequestChain, payload, false); err != nil { - return 0, nil, fmt.Errorf("write request_chain: %w", err) + return 0, nil, coreerr.E("LevinP2PConn.RequestChain", "write request_chain", err) } // Read until we get RESPONSE_CHAIN_ENTRY. for { hdr, data, err := c.conn.ReadPacket() if err != nil { - return 0, nil, fmt.Errorf("read response_chain: %w", err) + return 0, nil, coreerr.E("LevinP2PConn.RequestChain", "read response_chain", err) } if hdr.Command == p2p.CommandResponseChain { var resp p2p.ResponseChainEntry if err := resp.Decode(data); err != nil { - return 0, nil, fmt.Errorf("decode response_chain: %w", err) + return 0, nil, coreerr.E("LevinP2PConn.RequestChain", "decode response_chain", err) } return resp.StartHeight, resp.BlockIDs, nil } @@ -86,23 +87,23 @@ func (c *LevinP2PConn) RequestObjects(blockHashes [][]byte) ([]BlockBlobEntry, e req := p2p.RequestGetObjects{Blocks: blockHashes} payload, err := req.Encode() if err != nil { - return nil, fmt.Errorf("encode request_get_objects: %w", err) + return nil, coreerr.E("LevinP2PConn.RequestObjects", "encode request_get_objects", err) } if err := c.conn.WritePacket(p2p.CommandRequestObjects, payload, false); err != nil { - return nil, fmt.Errorf("write request_get_objects: %w", err) + return nil, coreerr.E("LevinP2PConn.RequestObjects", "write request_get_objects", err) } // Read until we get RESPONSE_GET_OBJECTS. for { hdr, data, err := c.conn.ReadPacket() if err != nil { - return nil, fmt.Errorf("read response_get_objects: %w", err) + return nil, coreerr.E("LevinP2PConn.RequestObjects", "read response_get_objects", err) } if hdr.Command == p2p.CommandResponseObjects { var resp p2p.ResponseGetObjects if err := resp.Decode(data); err != nil { - return nil, fmt.Errorf("decode response_get_objects: %w", err) + return nil, coreerr.E("LevinP2PConn.RequestObjects", "decode response_get_objects", err) } entries := make([]BlockBlobEntry, len(resp.Blocks)) for i, b := range resp.Blocks { diff --git a/chain/p2psync.go b/chain/p2psync.go index 90ef1ab..6a3cad4 100644 --- a/chain/p2psync.go +++ b/chain/p2psync.go @@ -9,6 +9,8 @@ import ( "context" "fmt" "log" + + coreerr "forge.lthn.ai/core/go-log" ) // P2PConnection abstracts the P2P communication needed for block sync. @@ -44,7 +46,7 @@ func (c *Chain) P2PSync(ctx context.Context, conn P2PConnection, opts SyncOption localHeight, err := c.Height() if err != nil { - return fmt.Errorf("p2p sync: get height: %w", err) + return coreerr.E("Chain.P2PSync", "p2p sync: get height", err) } peerHeight := conn.PeerHeight() @@ -55,7 +57,7 @@ func (c *Chain) P2PSync(ctx context.Context, conn P2PConnection, opts SyncOption // Build sparse chain history. history, err := c.SparseChainHistory() if err != nil { - return fmt.Errorf("p2p sync: build history: %w", err) + return coreerr.E("Chain.P2PSync", "p2p sync: build history", err) } // Convert Hash to []byte for P2P. @@ -69,7 +71,7 @@ func (c *Chain) P2PSync(ctx context.Context, conn P2PConnection, opts SyncOption // Request chain entry. startHeight, blockIDs, err := conn.RequestChain(historyBytes) if err != nil { - return fmt.Errorf("p2p sync: request chain: %w", err) + return coreerr.E("Chain.P2PSync", "p2p sync: request chain", err) } if len(blockIDs) == 0 { @@ -106,7 +108,7 @@ func (c *Chain) P2PSync(ctx context.Context, conn P2PConnection, opts SyncOption entries, err := conn.RequestObjects(batch) if err != nil { - return fmt.Errorf("p2p sync: request objects: %w", err) + return coreerr.E("Chain.P2PSync", "p2p sync: request objects", err) } currentHeight := fetchStart + uint64(i) @@ -118,12 +120,12 @@ func (c *Chain) P2PSync(ctx context.Context, conn P2PConnection, opts SyncOption blockDiff, err := c.NextDifficulty(blockHeight, opts.Forks) if err != nil { - return fmt.Errorf("p2p sync: compute difficulty for block %d: %w", blockHeight, err) + return coreerr.E("Chain.P2PSync", fmt.Sprintf("p2p sync: compute difficulty for block %d", blockHeight), err) } if err := c.processBlockBlobs(entry.Block, entry.Txs, blockHeight, blockDiff, opts); err != nil { - return fmt.Errorf("p2p sync: process block %d: %w", blockHeight, err) + return coreerr.E("Chain.P2PSync", fmt.Sprintf("p2p sync: process block %d", blockHeight), err) } } } diff --git a/chain/ring.go b/chain/ring.go index 0278863..acee086 100644 --- a/chain/ring.go +++ b/chain/ring.go @@ -8,6 +8,8 @@ package chain import ( "fmt" + coreerr "forge.lthn.ai/core/go-log" + "forge.lthn.ai/core/go-blockchain/consensus" "forge.lthn.ai/core/go-blockchain/types" ) @@ -20,17 +22,16 @@ func (c *Chain) GetRingOutputs(amount uint64, offsets []uint64) ([]types.PublicK for i, gidx := range offsets { txHash, outNo, err := c.GetOutput(amount, gidx) if err != nil { - return nil, fmt.Errorf("ring output %d (amount=%d, gidx=%d): %w", i, amount, gidx, err) + return nil, coreerr.E("Chain.GetRingOutputs", fmt.Sprintf("ring output %d (amount=%d, gidx=%d)", i, amount, gidx), err) } tx, _, err := c.GetTransaction(txHash) if err != nil { - return nil, fmt.Errorf("ring output %d: tx %s: %w", i, txHash, err) + return nil, coreerr.E("Chain.GetRingOutputs", fmt.Sprintf("ring output %d: tx %s", i, txHash), err) } if int(outNo) >= len(tx.Vout) { - return nil, fmt.Errorf("ring output %d: tx %s has %d outputs, want index %d", - i, txHash, len(tx.Vout), outNo) + return nil, coreerr.E("Chain.GetRingOutputs", fmt.Sprintf("ring output %d: tx %s has %d outputs, want index %d", i, txHash, len(tx.Vout), outNo), nil) } switch out := tx.Vout[outNo].(type) { @@ -41,7 +42,7 @@ func (c *Chain) GetRingOutputs(amount uint64, offsets []uint64) ([]types.PublicK } pubs[i] = toKey.Key default: - return nil, fmt.Errorf("ring output %d: unsupported output type %T", i, out) + return nil, coreerr.E("Chain.GetRingOutputs", fmt.Sprintf("ring output %d: unsupported output type %T", i, out), nil) } } return pubs, nil @@ -57,17 +58,16 @@ func (c *Chain) GetZCRingOutputs(offsets []uint64) ([]consensus.ZCRingMember, er for i, gidx := range offsets { txHash, outNo, err := c.GetOutput(0, gidx) if err != nil { - return nil, fmt.Errorf("ZC ring output %d (gidx=%d): %w", i, gidx, err) + return nil, coreerr.E("Chain.GetZCRingOutputs", fmt.Sprintf("ZC ring output %d (gidx=%d)", i, gidx), err) } tx, _, err := c.GetTransaction(txHash) if err != nil { - return nil, fmt.Errorf("ZC ring output %d: tx %s: %w", i, txHash, err) + return nil, coreerr.E("Chain.GetZCRingOutputs", fmt.Sprintf("ZC ring output %d: tx %s", i, txHash), err) } if int(outNo) >= len(tx.Vout) { - return nil, fmt.Errorf("ZC ring output %d: tx %s has %d outputs, want index %d", - i, txHash, len(tx.Vout), outNo) + return nil, coreerr.E("Chain.GetZCRingOutputs", fmt.Sprintf("ZC ring output %d: tx %s has %d outputs, want index %d", i, txHash, len(tx.Vout), outNo), nil) } switch out := tx.Vout[outNo].(type) { @@ -78,7 +78,7 @@ func (c *Chain) GetZCRingOutputs(offsets []uint64) ([]consensus.ZCRingMember, er BlindedAssetID: [32]byte(out.BlindedAssetID), } default: - return nil, fmt.Errorf("ZC ring output %d: expected TxOutputZarcanum, got %T", i, out) + return nil, coreerr.E("Chain.GetZCRingOutputs", fmt.Sprintf("ZC ring output %d: expected TxOutputZarcanum, got %T", i, out), nil) } } return members, nil diff --git a/chain/store.go b/chain/store.go index 12bd920..53fe068 100644 --- a/chain/store.go +++ b/chain/store.go @@ -13,9 +13,11 @@ import ( "fmt" "strconv" - store "forge.lthn.ai/core/go-store" + coreerr "forge.lthn.ai/core/go-log" + "forge.lthn.ai/core/go-blockchain/types" "forge.lthn.ai/core/go-blockchain/wire" + store "forge.lthn.ai/core/go-store" ) // Storage group constants matching the design schema. @@ -44,7 +46,7 @@ func (c *Chain) PutBlock(b *types.Block, meta *BlockMeta) error { enc := wire.NewEncoder(&buf) wire.EncodeBlock(enc, b) if err := enc.Err(); err != nil { - return fmt.Errorf("chain: encode block %d: %w", meta.Height, err) + return coreerr.E("Chain.PutBlock", fmt.Sprintf("chain: encode block %d", meta.Height), err) } rec := blockRecord{ @@ -53,17 +55,17 @@ func (c *Chain) PutBlock(b *types.Block, meta *BlockMeta) error { } val, err := json.Marshal(rec) if err != nil { - return fmt.Errorf("chain: marshal block %d: %w", meta.Height, err) + return coreerr.E("Chain.PutBlock", fmt.Sprintf("chain: marshal block %d", meta.Height), err) } if err := c.store.Set(groupBlocks, heightKey(meta.Height), string(val)); err != nil { - return fmt.Errorf("chain: store block %d: %w", meta.Height, err) + return coreerr.E("Chain.PutBlock", fmt.Sprintf("chain: store block %d", meta.Height), err) } // Update hash -> height index. hashHex := meta.Hash.String() if err := c.store.Set(groupBlockIndex, hashHex, strconv.FormatUint(meta.Height, 10)); err != nil { - return fmt.Errorf("chain: index block %d: %w", meta.Height, err) + return coreerr.E("Chain.PutBlock", fmt.Sprintf("chain: index block %d", meta.Height), err) } return nil @@ -74,9 +76,9 @@ func (c *Chain) GetBlockByHeight(height uint64) (*types.Block, *BlockMeta, error val, err := c.store.Get(groupBlocks, heightKey(height)) if err != nil { if errors.Is(err, store.ErrNotFound) { - return nil, nil, fmt.Errorf("chain: block %d not found", height) + return nil, nil, coreerr.E("Chain.GetBlockByHeight", fmt.Sprintf("chain: block %d not found", height), nil) } - return nil, nil, fmt.Errorf("chain: get block %d: %w", height, err) + return nil, nil, coreerr.E("Chain.GetBlockByHeight", fmt.Sprintf("chain: get block %d", height), err) } return decodeBlockRecord(val) } @@ -86,13 +88,13 @@ func (c *Chain) GetBlockByHash(hash types.Hash) (*types.Block, *BlockMeta, error heightStr, err := c.store.Get(groupBlockIndex, hash.String()) if err != nil { if errors.Is(err, store.ErrNotFound) { - return nil, nil, fmt.Errorf("chain: block %s not found", hash) + return nil, nil, coreerr.E("Chain.GetBlockByHash", fmt.Sprintf("chain: block %s not found", hash), nil) } - return nil, nil, fmt.Errorf("chain: get block index %s: %w", hash, err) + return nil, nil, coreerr.E("Chain.GetBlockByHash", fmt.Sprintf("chain: get block index %s", hash), err) } height, err := strconv.ParseUint(heightStr, 10, 64) if err != nil { - return nil, nil, fmt.Errorf("chain: parse height %q: %w", heightStr, err) + return nil, nil, coreerr.E("Chain.GetBlockByHash", fmt.Sprintf("chain: parse height %q", heightStr), err) } return c.GetBlockByHeight(height) } @@ -109,7 +111,7 @@ func (c *Chain) PutTransaction(hash types.Hash, tx *types.Transaction, meta *TxM enc := wire.NewEncoder(&buf) wire.EncodeTransaction(enc, tx) if err := enc.Err(); err != nil { - return fmt.Errorf("chain: encode tx %s: %w", hash, err) + return coreerr.E("Chain.PutTransaction", fmt.Sprintf("chain: encode tx %s", hash), err) } rec := txRecord{ @@ -118,11 +120,11 @@ func (c *Chain) PutTransaction(hash types.Hash, tx *types.Transaction, meta *TxM } val, err := json.Marshal(rec) if err != nil { - return fmt.Errorf("chain: marshal tx %s: %w", hash, err) + return coreerr.E("Chain.PutTransaction", fmt.Sprintf("chain: marshal tx %s", hash), err) } if err := c.store.Set(groupTx, hash.String(), string(val)); err != nil { - return fmt.Errorf("chain: store tx %s: %w", hash, err) + return coreerr.E("Chain.PutTransaction", fmt.Sprintf("chain: store tx %s", hash), err) } return nil } @@ -132,23 +134,23 @@ func (c *Chain) GetTransaction(hash types.Hash) (*types.Transaction, *TxMeta, er val, err := c.store.Get(groupTx, hash.String()) if err != nil { if errors.Is(err, store.ErrNotFound) { - return nil, nil, fmt.Errorf("chain: tx %s not found", hash) + return nil, nil, coreerr.E("Chain.GetTransaction", fmt.Sprintf("chain: tx %s not found", hash), nil) } - return nil, nil, fmt.Errorf("chain: get tx %s: %w", hash, err) + return nil, nil, coreerr.E("Chain.GetTransaction", fmt.Sprintf("chain: get tx %s", hash), err) } var rec txRecord if err := json.Unmarshal([]byte(val), &rec); err != nil { - return nil, nil, fmt.Errorf("chain: unmarshal tx: %w", err) + return nil, nil, coreerr.E("Chain.GetTransaction", "chain: unmarshal tx", err) } blob, err := hex.DecodeString(rec.Blob) if err != nil { - return nil, nil, fmt.Errorf("chain: decode tx hex: %w", err) + return nil, nil, coreerr.E("Chain.GetTransaction", "chain: decode tx hex", err) } dec := wire.NewDecoder(bytes.NewReader(blob)) tx := wire.DecodeTransaction(dec) if err := dec.Err(); err != nil { - return nil, nil, fmt.Errorf("chain: decode tx wire: %w", err) + return nil, nil, coreerr.E("Chain.GetTransaction", "chain: decode tx wire", err) } return &tx, &rec.Meta, nil } @@ -164,11 +166,11 @@ func (c *Chain) HasTransaction(hash types.Hash) bool { func (c *Chain) getBlockMeta(height uint64) (*BlockMeta, error) { val, err := c.store.Get(groupBlocks, heightKey(height)) if err != nil { - return nil, fmt.Errorf("chain: block meta %d: %w", height, err) + return nil, coreerr.E("Chain.getBlockMeta", fmt.Sprintf("chain: block meta %d", height), err) } var rec blockRecord if err := json.Unmarshal([]byte(val), &rec); err != nil { - return nil, fmt.Errorf("chain: unmarshal block meta %d: %w", height, err) + return nil, coreerr.E("Chain.getBlockMeta", fmt.Sprintf("chain: unmarshal block meta %d", height), err) } return &rec.Meta, nil } @@ -176,16 +178,16 @@ func (c *Chain) getBlockMeta(height uint64) (*BlockMeta, error) { func decodeBlockRecord(val string) (*types.Block, *BlockMeta, error) { var rec blockRecord if err := json.Unmarshal([]byte(val), &rec); err != nil { - return nil, nil, fmt.Errorf("chain: unmarshal block: %w", err) + return nil, nil, coreerr.E("decodeBlockRecord", "chain: unmarshal block", err) } blob, err := hex.DecodeString(rec.Blob) if err != nil { - return nil, nil, fmt.Errorf("chain: decode block hex: %w", err) + return nil, nil, coreerr.E("decodeBlockRecord", "chain: decode block hex", err) } dec := wire.NewDecoder(bytes.NewReader(blob)) blk := wire.DecodeBlock(dec) if err := dec.Err(); err != nil { - return nil, nil, fmt.Errorf("chain: decode block wire: %w", err) + return nil, nil, coreerr.E("decodeBlockRecord", "chain: decode block wire", err) } return &blk, &rec.Meta, nil } diff --git a/chain/sync.go b/chain/sync.go index f795fec..b4a50ff 100644 --- a/chain/sync.go +++ b/chain/sync.go @@ -10,12 +10,13 @@ import ( "context" "encoding/hex" "encoding/json" - "errors" "fmt" "log" "regexp" "strconv" + coreerr "forge.lthn.ai/core/go-log" + "forge.lthn.ai/core/go-blockchain/config" "forge.lthn.ai/core/go-blockchain/consensus" "forge.lthn.ai/core/go-blockchain/rpc" @@ -51,12 +52,12 @@ func DefaultSyncOptions() SyncOptions { func (c *Chain) Sync(ctx context.Context, client *rpc.Client, opts SyncOptions) error { localHeight, err := c.Height() if err != nil { - return fmt.Errorf("sync: get local height: %w", err) + return coreerr.E("Chain.Sync", "sync: get local height", err) } remoteHeight, err := client.GetHeight() if err != nil { - return fmt.Errorf("sync: get remote height: %w", err) + return coreerr.E("Chain.Sync", "sync: get remote height", err) } for localHeight < remoteHeight { @@ -71,22 +72,22 @@ func (c *Chain) Sync(ctx context.Context, client *rpc.Client, opts SyncOptions) blocks, err := client.GetBlocksDetails(localHeight, batch) if err != nil { - return fmt.Errorf("sync: fetch blocks at %d: %w", localHeight, err) + return coreerr.E("Chain.Sync", fmt.Sprintf("sync: fetch blocks at %d", localHeight), err) } if err := resolveBlockBlobs(blocks, client); err != nil { - return fmt.Errorf("sync: resolve blobs at %d: %w", localHeight, err) + return coreerr.E("Chain.Sync", fmt.Sprintf("sync: resolve blobs at %d", localHeight), err) } for _, bd := range blocks { if err := c.processBlock(bd, opts); err != nil { - return fmt.Errorf("sync: process block %d: %w", bd.Height, err) + return coreerr.E("Chain.Sync", fmt.Sprintf("sync: process block %d", bd.Height), err) } } localHeight, err = c.Height() if err != nil { - return fmt.Errorf("sync: get height after batch: %w", err) + return coreerr.E("Chain.Sync", "sync: get height after batch", err) } } @@ -100,7 +101,7 @@ func (c *Chain) processBlock(bd rpc.BlockDetails, opts SyncOptions) error { blockBlob, err := hex.DecodeString(bd.Blob) if err != nil { - return fmt.Errorf("decode block hex: %w", err) + return coreerr.E("Chain.processBlock", "decode block hex", err) } // Build a set of the block's regular tx hashes for lookup. @@ -110,7 +111,7 @@ func (c *Chain) processBlock(bd rpc.BlockDetails, opts SyncOptions) error { dec := wire.NewDecoder(bytes.NewReader(blockBlob)) blk := wire.DecodeBlock(dec) if err := dec.Err(); err != nil { - return fmt.Errorf("decode block for tx hashes: %w", err) + return coreerr.E("Chain.processBlock", "decode block for tx hashes", err) } regularTxs := make(map[string]struct{}, len(blk.TxHashes)) @@ -125,7 +126,7 @@ func (c *Chain) processBlock(bd rpc.BlockDetails, opts SyncOptions) error { } txBlobBytes, err := hex.DecodeString(txInfo.Blob) if err != nil { - return fmt.Errorf("decode tx hex %s: %w", txInfo.ID, err) + return coreerr.E("Chain.processBlock", fmt.Sprintf("decode tx hex %s", txInfo.ID), err) } txBlobs = append(txBlobs, txBlobBytes) } @@ -136,11 +137,10 @@ func (c *Chain) processBlock(bd rpc.BlockDetails, opts SyncOptions) error { computedHash := wire.BlockHash(&blk) daemonHash, err := types.HashFromHex(bd.ID) if err != nil { - return fmt.Errorf("parse daemon block hash: %w", err) + return coreerr.E("Chain.processBlock", "parse daemon block hash", err) } if computedHash != daemonHash { - return fmt.Errorf("block hash mismatch: computed %s, daemon says %s", - computedHash, daemonHash) + return coreerr.E("Chain.processBlock", fmt.Sprintf("block hash mismatch: computed %s, daemon says %s", computedHash, daemonHash), nil) } return c.processBlockBlobs(blockBlob, txBlobs, bd.Height, diff, opts) @@ -155,7 +155,7 @@ func (c *Chain) processBlockBlobs(blockBlob []byte, txBlobs [][]byte, dec := wire.NewDecoder(bytes.NewReader(blockBlob)) blk := wire.DecodeBlock(dec) if err := dec.Err(); err != nil { - return fmt.Errorf("decode block wire: %w", err) + return coreerr.E("Chain.processBlockBlobs", "decode block wire", err) } // Compute the block hash. @@ -165,11 +165,10 @@ func (c *Chain) processBlockBlobs(blockBlob []byte, txBlobs [][]byte, if height == 0 { genesisHash, err := types.HashFromHex(GenesisHash) if err != nil { - return fmt.Errorf("parse genesis hash: %w", err) + return coreerr.E("Chain.processBlockBlobs", "parse genesis hash", err) } if blockHash != genesisHash { - return fmt.Errorf("genesis hash %s does not match expected %s", - blockHash, GenesisHash) + return coreerr.E("Chain.processBlockBlobs", fmt.Sprintf("genesis hash %s does not match expected %s", blockHash, GenesisHash), nil) } } @@ -180,7 +179,7 @@ func (c *Chain) processBlockBlobs(blockBlob []byte, txBlobs [][]byte, // Validate miner transaction structure. if err := consensus.ValidateMinerTx(&blk.MinerTx, height, opts.Forks); err != nil { - return fmt.Errorf("validate miner tx: %w", err) + return coreerr.E("Chain.processBlockBlobs", "validate miner tx", err) } // Calculate cumulative difficulty. @@ -188,7 +187,7 @@ func (c *Chain) processBlockBlobs(blockBlob []byte, txBlobs [][]byte, if height > 0 { _, prevMeta, err := c.TopBlock() if err != nil { - return fmt.Errorf("get prev block meta: %w", err) + return coreerr.E("Chain.processBlockBlobs", "get prev block meta", err) } cumulDiff = prevMeta.CumulativeDiff + difficulty } else { @@ -199,13 +198,13 @@ func (c *Chain) processBlockBlobs(blockBlob []byte, txBlobs [][]byte, minerTxHash := wire.TransactionHash(&blk.MinerTx) minerGindexes, err := c.indexOutputs(minerTxHash, &blk.MinerTx) if err != nil { - return fmt.Errorf("index miner tx outputs: %w", err) + return coreerr.E("Chain.processBlockBlobs", "index miner tx outputs", err) } if err := c.PutTransaction(minerTxHash, &blk.MinerTx, &TxMeta{ KeeperBlock: height, GlobalOutputIndexes: minerGindexes, }); err != nil { - return fmt.Errorf("store miner tx: %w", err) + return coreerr.E("Chain.processBlockBlobs", "store miner tx", err) } // Process regular transactions from txBlobs. @@ -213,27 +212,27 @@ func (c *Chain) processBlockBlobs(blockBlob []byte, txBlobs [][]byte, txDec := wire.NewDecoder(bytes.NewReader(txBlobData)) tx := wire.DecodeTransaction(txDec) if err := txDec.Err(); err != nil { - return fmt.Errorf("decode tx wire [%d]: %w", i, err) + return coreerr.E("Chain.processBlockBlobs", fmt.Sprintf("decode tx wire [%d]", i), err) } txHash := wire.TransactionHash(&tx) // Validate transaction semantics. if err := consensus.ValidateTransaction(&tx, txBlobData, opts.Forks, height); err != nil { - return fmt.Errorf("validate tx %s: %w", txHash, err) + return coreerr.E("Chain.processBlockBlobs", fmt.Sprintf("validate tx %s", txHash), err) } // Optionally verify signatures using the chain's output index. if opts.VerifySignatures { if err := consensus.VerifyTransactionSignatures(&tx, opts.Forks, height, c.GetRingOutputs, c.GetZCRingOutputs); err != nil { - return fmt.Errorf("verify tx signatures %s: %w", txHash, err) + return coreerr.E("Chain.processBlockBlobs", fmt.Sprintf("verify tx signatures %s", txHash), err) } } // Index outputs. gindexes, err := c.indexOutputs(txHash, &tx) if err != nil { - return fmt.Errorf("index tx outputs %s: %w", txHash, err) + return coreerr.E("Chain.processBlockBlobs", fmt.Sprintf("index tx outputs %s", txHash), err) } // Mark key images as spent. @@ -241,11 +240,11 @@ func (c *Chain) processBlockBlobs(blockBlob []byte, txBlobs [][]byte, switch inp := vin.(type) { case types.TxInputToKey: if err := c.MarkSpent(inp.KeyImage, height); err != nil { - return fmt.Errorf("mark spent %s: %w", inp.KeyImage, err) + return coreerr.E("Chain.processBlockBlobs", fmt.Sprintf("mark spent %s", inp.KeyImage), err) } case types.TxInputZC: if err := c.MarkSpent(inp.KeyImage, height); err != nil { - return fmt.Errorf("mark spent %s: %w", inp.KeyImage, err) + return coreerr.E("Chain.processBlockBlobs", fmt.Sprintf("mark spent %s", inp.KeyImage), err) } } } @@ -255,7 +254,7 @@ func (c *Chain) processBlockBlobs(blockBlob []byte, txBlobs [][]byte, KeeperBlock: height, GlobalOutputIndexes: gindexes, }); err != nil { - return fmt.Errorf("store tx %s: %w", txHash, err) + return coreerr.E("Chain.processBlockBlobs", fmt.Sprintf("store tx %s", txHash), err) } } @@ -330,13 +329,13 @@ func resolveBlockBlobs(blocks []rpc.BlockDetails, client *rpc.Client) error { // Batch-fetch tx blobs. txHexes, missed, err := client.GetTransactions(allHashes) if err != nil { - return fmt.Errorf("fetch tx blobs: %w", err) + return coreerr.E("resolveBlockBlobs", "fetch tx blobs", err) } if len(missed) > 0 { - return fmt.Errorf("daemon missed %d tx(es): %v", len(missed), missed) + return coreerr.E("resolveBlockBlobs", fmt.Sprintf("daemon missed %d tx(es): %v", len(missed), missed), nil) } if len(txHexes) != len(allHashes) { - return fmt.Errorf("expected %d tx blobs, got %d", len(allHashes), len(txHexes)) + return coreerr.E("resolveBlockBlobs", fmt.Sprintf("expected %d tx blobs, got %d", len(allHashes), len(txHexes)), nil) } // Index fetched blobs by hash. @@ -364,16 +363,16 @@ func resolveBlockBlobs(blocks []rpc.BlockDetails, client *rpc.Client) error { // Parse header from object_in_json. hdr, err := parseBlockHeader(bd.ObjectInJSON) if err != nil { - return fmt.Errorf("block %d: parse header: %w", bd.Height, err) + return coreerr.E("resolveBlockBlobs", fmt.Sprintf("block %d: parse header", bd.Height), err) } // Miner tx blob is transactions_details[0]. if len(bd.Transactions) == 0 { - return fmt.Errorf("block %d has no transactions_details", bd.Height) + return coreerr.E("resolveBlockBlobs", fmt.Sprintf("block %d has no transactions_details", bd.Height), nil) } minerTxBlob, err := hex.DecodeString(bd.Transactions[0].Blob) if err != nil { - return fmt.Errorf("block %d: decode miner tx hex: %w", bd.Height, err) + return coreerr.E("resolveBlockBlobs", fmt.Sprintf("block %d: decode miner tx hex", bd.Height), err) } // Collect regular tx hashes. @@ -381,7 +380,7 @@ func resolveBlockBlobs(blocks []rpc.BlockDetails, client *rpc.Client) error { for _, txInfo := range bd.Transactions[1:] { h, err := types.HashFromHex(txInfo.ID) if err != nil { - return fmt.Errorf("block %d: parse tx hash %s: %w", bd.Height, txInfo.ID, err) + return coreerr.E("resolveBlockBlobs", fmt.Sprintf("block %d: parse tx hash %s", bd.Height, txInfo.ID), err) } txHashes = append(txHashes, h) } @@ -411,17 +410,17 @@ var aggregatedRE = regexp.MustCompile(`"AGGREGATED"\s*:\s*\{([^}]+)\}`) func parseBlockHeader(objectInJSON string) (*types.BlockHeader, error) { m := aggregatedRE.FindStringSubmatch(objectInJSON) if m == nil { - return nil, errors.New("AGGREGATED section not found in object_in_json") + return nil, coreerr.E("parseBlockHeader", "AGGREGATED section not found in object_in_json", nil) } var hj blockHeaderJSON if err := json.Unmarshal([]byte("{"+m[1]+"}"), &hj); err != nil { - return nil, fmt.Errorf("unmarshal AGGREGATED: %w", err) + return nil, coreerr.E("parseBlockHeader", "unmarshal AGGREGATED", err) } prevID, err := types.HashFromHex(hj.PrevID) if err != nil { - return nil, fmt.Errorf("parse prev_id: %w", err) + return nil, coreerr.E("parseBlockHeader", "parse prev_id", err) } return &types.BlockHeader{ diff --git a/chain/validate.go b/chain/validate.go index a52a845..83ef21e 100644 --- a/chain/validate.go +++ b/chain/validate.go @@ -7,9 +7,10 @@ package chain import ( "bytes" - "errors" "fmt" + coreerr "forge.lthn.ai/core/go-log" + "forge.lthn.ai/core/go-blockchain/config" "forge.lthn.ai/core/go-blockchain/types" "forge.lthn.ai/core/go-blockchain/wire" @@ -20,19 +21,18 @@ import ( func (c *Chain) ValidateHeader(b *types.Block, expectedHeight uint64) error { currentHeight, err := c.Height() if err != nil { - return fmt.Errorf("validate: get height: %w", err) + return coreerr.E("Chain.ValidateHeader", "validate: get height", err) } // Height sequence check. if expectedHeight != currentHeight { - return fmt.Errorf("validate: expected height %d but chain is at %d", - expectedHeight, currentHeight) + return coreerr.E("Chain.ValidateHeader", fmt.Sprintf("validate: expected height %d but chain is at %d", expectedHeight, currentHeight), nil) } // Genesis block: prev_id must be zero. if expectedHeight == 0 { if !b.PrevID.IsZero() { - return errors.New("validate: genesis block has non-zero prev_id") + return coreerr.E("Chain.ValidateHeader", "validate: genesis block has non-zero prev_id", nil) } return nil } @@ -40,11 +40,10 @@ func (c *Chain) ValidateHeader(b *types.Block, expectedHeight uint64) error { // Non-genesis: prev_id must match top block hash. _, topMeta, err := c.TopBlock() if err != nil { - return fmt.Errorf("validate: get top block: %w", err) + return coreerr.E("Chain.ValidateHeader", "validate: get top block", err) } if b.PrevID != topMeta.Hash { - return fmt.Errorf("validate: prev_id %s does not match top block %s", - b.PrevID, topMeta.Hash) + return coreerr.E("Chain.ValidateHeader", fmt.Sprintf("validate: prev_id %s does not match top block %s", b.PrevID, topMeta.Hash), nil) } // Block size check. @@ -52,8 +51,7 @@ func (c *Chain) ValidateHeader(b *types.Block, expectedHeight uint64) error { enc := wire.NewEncoder(&buf) wire.EncodeBlock(enc, b) if enc.Err() == nil && uint64(buf.Len()) > config.MaxBlockSize { - return fmt.Errorf("validate: block size %d exceeds max %d", - buf.Len(), config.MaxBlockSize) + return coreerr.E("Chain.ValidateHeader", fmt.Sprintf("validate: block size %d exceeds max %d", buf.Len(), config.MaxBlockSize), nil) } return nil diff --git a/cmd_explorer.go b/cmd_explorer.go index 933d17c..dd1c153 100644 --- a/cmd_explorer.go +++ b/cmd_explorer.go @@ -7,12 +7,13 @@ package blockchain import ( "context" - "fmt" "os" "os/signal" "path/filepath" "sync" + coreerr "forge.lthn.ai/core/go-log" + cli "forge.lthn.ai/core/cli/pkg/cli" store "forge.lthn.ai/core/go-store" @@ -40,7 +41,7 @@ func runExplorer(dataDir, seed string, testnet bool) error { dbPath := filepath.Join(dataDir, "chain.db") s, err := store.New(dbPath) if err != nil { - return fmt.Errorf("open store: %w", err) + return coreerr.E("runExplorer", "open store", err) } defer s.Close() @@ -68,7 +69,7 @@ func runExplorer(dataDir, seed string, testnet bool) error { frame.Footer(hints) frame.Run() - cancel() // Signal syncLoop to stop. + cancel() // Signal syncLoop to stop. wg.Wait() // Wait for it before closing store. return nil } diff --git a/cmd_sync.go b/cmd_sync.go index 06e4b0f..df5195f 100644 --- a/cmd_sync.go +++ b/cmd_sync.go @@ -15,6 +15,8 @@ import ( "sync" "syscall" + coreerr "forge.lthn.ai/core/go-log" + "forge.lthn.ai/core/go-blockchain/chain" "forge.lthn.ai/core/go-process" store "forge.lthn.ai/core/go-store" @@ -56,7 +58,7 @@ func runSyncForeground(dataDir, seed string, testnet bool) error { dbPath := filepath.Join(dataDir, "chain.db") s, err := store.New(dbPath) if err != nil { - return fmt.Errorf("open store: %w", err) + return coreerr.E("runSyncForeground", "open store", err) } defer s.Close() @@ -89,14 +91,14 @@ func runSyncDaemon(dataDir, seed string, testnet bool) error { }) if err := d.Start(); err != nil { - return fmt.Errorf("daemon start: %w", err) + return coreerr.E("runSyncDaemon", "daemon start", err) } dbPath := filepath.Join(dataDir, "chain.db") s, err := store.New(dbPath) if err != nil { _ = d.Stop() - return fmt.Errorf("open store: %w", err) + return coreerr.E("runSyncDaemon", "open store", err) } defer s.Close() @@ -125,16 +127,16 @@ func stopSyncDaemon(dataDir string) error { pidFile := filepath.Join(dataDir, "sync.pid") pid, running := process.ReadPID(pidFile) if pid == 0 || !running { - return fmt.Errorf("no running sync daemon found") + return coreerr.E("stopSyncDaemon", "no running sync daemon found", nil) } proc, err := os.FindProcess(pid) if err != nil { - return fmt.Errorf("find process %d: %w", pid, err) + return coreerr.E("stopSyncDaemon", fmt.Sprintf("find process %d", pid), err) } if err := proc.Signal(syscall.SIGTERM); err != nil { - return fmt.Errorf("signal process %d: %w", pid, err) + return coreerr.E("stopSyncDaemon", fmt.Sprintf("signal process %d", pid), err) } log.Printf("Sent SIGTERM to sync daemon (PID %d)", pid) diff --git a/commands.go b/commands.go index 492cec2..f3274f6 100644 --- a/commands.go +++ b/commands.go @@ -6,10 +6,12 @@ package blockchain import ( - "fmt" "os" "path/filepath" + coreio "forge.lthn.ai/core/go-io" + coreerr "forge.lthn.ai/core/go-log" + "forge.lthn.ai/core/go-blockchain/config" "github.com/spf13/cobra" ) @@ -60,8 +62,8 @@ func defaultDataDir() string { } func ensureDataDir(dataDir string) error { - if err := os.MkdirAll(dataDir, 0o755); err != nil { - return fmt.Errorf("create data dir: %w", err) + if err := coreio.Local.EnsureDir(dataDir); err != nil { + return coreerr.E("ensureDataDir", "create data dir", err) } return nil } diff --git a/consensus/block.go b/consensus/block.go index e8f6a9e..5345b6f 100644 --- a/consensus/block.go +++ b/consensus/block.go @@ -9,6 +9,8 @@ import ( "fmt" "slices" + coreerr "forge.lthn.ai/core/go-log" + "forge.lthn.ai/core/go-blockchain/config" "forge.lthn.ai/core/go-blockchain/types" ) @@ -28,8 +30,8 @@ func CheckTimestamp(blockTimestamp uint64, flags uint8, adjustedTime uint64, rec limit = config.PosBlockFutureTimeLimit } if blockTimestamp > adjustedTime+limit { - return fmt.Errorf("%w: %d > %d + %d", ErrTimestampFuture, - blockTimestamp, adjustedTime, limit) + return coreerr.E("CheckTimestamp", fmt.Sprintf("%d > %d + %d", + blockTimestamp, adjustedTime, limit), ErrTimestampFuture) } // Median check — only when we have enough history. @@ -39,8 +41,8 @@ func CheckTimestamp(blockTimestamp uint64, flags uint8, adjustedTime uint64, rec median := medianTimestamp(recentTimestamps) if blockTimestamp < median { - return fmt.Errorf("%w: %d < median %d", ErrTimestampOld, - blockTimestamp, median) + return coreerr.E("CheckTimestamp", fmt.Sprintf("%d < median %d", + blockTimestamp, median), ErrTimestampOld) } return nil @@ -64,16 +66,16 @@ func medianTimestamp(timestamps []uint64) uint64 { // 2 inputs (TxInputGenesis + stake input). func ValidateMinerTx(tx *types.Transaction, height uint64, forks []config.HardFork) error { if len(tx.Vin) == 0 { - return fmt.Errorf("%w: no inputs", ErrMinerTxInputs) + return coreerr.E("ValidateMinerTx", "no inputs", ErrMinerTxInputs) } // First input must be TxInputGenesis. gen, ok := tx.Vin[0].(types.TxInputGenesis) if !ok { - return fmt.Errorf("%w: first input is not txin_gen", ErrMinerTxInputs) + return coreerr.E("ValidateMinerTx", "first input is not txin_gen", ErrMinerTxInputs) } if gen.Height != height { - return fmt.Errorf("%w: got %d, expected %d", ErrMinerTxHeight, gen.Height, height) + return coreerr.E("ValidateMinerTx", fmt.Sprintf("got %d, expected %d", gen.Height, height), ErrMinerTxHeight) } // PoW blocks: exactly 1 input. PoS: exactly 2. @@ -87,12 +89,12 @@ func ValidateMinerTx(tx *types.Transaction, height uint64, forks []config.HardFo default: hf4Active := config.IsHardForkActive(forks, config.HF4Zarcanum, height) if !hf4Active { - return fmt.Errorf("%w: invalid PoS stake input type", ErrMinerTxInputs) + return coreerr.E("ValidateMinerTx", "invalid PoS stake input type", ErrMinerTxInputs) } // Post-HF4: accept ZC inputs. } } else { - return fmt.Errorf("%w: %d inputs (expected 1 or 2)", ErrMinerTxInputs, len(tx.Vin)) + return coreerr.E("ValidateMinerTx", fmt.Sprintf("%d inputs (expected 1 or 2)", len(tx.Vin)), ErrMinerTxInputs) } return nil @@ -119,7 +121,7 @@ func ValidateBlockReward(minerTx *types.Transaction, height, blockSize, medianSi } if outputSum > expected { - return fmt.Errorf("%w: outputs %d > expected %d", ErrRewardMismatch, outputSum, expected) + return coreerr.E("ValidateBlockReward", fmt.Sprintf("outputs %d > expected %d", outputSum, expected), ErrRewardMismatch) } return nil diff --git a/consensus/fee.go b/consensus/fee.go index b8b9bc8..8d561cb 100644 --- a/consensus/fee.go +++ b/consensus/fee.go @@ -9,6 +9,8 @@ import ( "fmt" "math" + coreerr "forge.lthn.ai/core/go-log" + "forge.lthn.ai/core/go-blockchain/types" ) @@ -31,7 +33,7 @@ func TxFee(tx *types.Transaction) (uint64, error) { } if outputSum > inputSum { - return 0, fmt.Errorf("%w: inputs=%d, outputs=%d", ErrNegativeFee, inputSum, outputSum) + return 0, coreerr.E("TxFee", fmt.Sprintf("inputs=%d, outputs=%d", inputSum, outputSum), ErrNegativeFee) } return inputSum - outputSum, nil diff --git a/consensus/reward.go b/consensus/reward.go index efdf0a2..560dd15 100644 --- a/consensus/reward.go +++ b/consensus/reward.go @@ -6,10 +6,11 @@ package consensus import ( - "errors" "fmt" "math/bits" + coreerr "forge.lthn.ai/core/go-log" + "forge.lthn.ai/core/go-blockchain/config" ) @@ -43,7 +44,7 @@ func BlockReward(baseReward, blockSize, medianSize uint64) (uint64, error) { } if blockSize > 2*effectiveMedian { - return 0, fmt.Errorf("consensus: block size %d too large for median %d", blockSize, effectiveMedian) + return 0, coreerr.E("BlockReward", fmt.Sprintf("consensus: block size %d too large for median %d", blockSize, effectiveMedian), nil) } // penalty = baseReward * (2*median - size) * size / median² @@ -56,7 +57,7 @@ func BlockReward(baseReward, blockSize, medianSize uint64) (uint64, error) { // Since hi1 should be 0 for reasonable block sizes, simplify: if hi1 > 0 { - return 0, errors.New("consensus: reward overflow") + return 0, coreerr.E("BlockReward", "consensus: reward overflow", nil) } hi2, lo2 := bits.Mul64(baseReward, lo1) diff --git a/consensus/tx.go b/consensus/tx.go index e9b462a..f978b2f 100644 --- a/consensus/tx.go +++ b/consensus/tx.go @@ -8,6 +8,8 @@ package consensus import ( "fmt" + coreerr "forge.lthn.ai/core/go-log" + "forge.lthn.ai/core/go-blockchain/config" "forge.lthn.ai/core/go-blockchain/types" ) @@ -15,17 +17,11 @@ import ( // ValidateTransaction performs semantic validation on a regular (non-coinbase) // transaction. Checks are ordered to match the C++ validate_tx_semantic(). func ValidateTransaction(tx *types.Transaction, txBlob []byte, forks []config.HardFork, height uint64) error { - hf1Active := config.IsHardForkActive(forks, config.HF1, height) hf4Active := config.IsHardForkActive(forks, config.HF4Zarcanum, height) - // 0. Transaction version for current hardfork. - if err := checkTxVersion(tx, forks, height); err != nil { - return err - } - // 1. Blob size. if uint64(len(txBlob)) >= config.MaxTransactionBlobSize { - return fmt.Errorf("%w: %d bytes", ErrTxTooLarge, len(txBlob)) + return coreerr.E("ValidateTransaction", fmt.Sprintf("%d bytes", len(txBlob)), ErrTxTooLarge) } // 2. Input count. @@ -33,16 +29,16 @@ func ValidateTransaction(tx *types.Transaction, txBlob []byte, forks []config.Ha return ErrNoInputs } if uint64(len(tx.Vin)) > config.TxMaxAllowedInputs { - return fmt.Errorf("%w: %d", ErrTooManyInputs, len(tx.Vin)) + return coreerr.E("ValidateTransaction", fmt.Sprintf("%d", len(tx.Vin)), ErrTooManyInputs) } // 3. Input types — TxInputGenesis not allowed in regular transactions. - if err := checkInputTypes(tx, hf1Active, hf4Active); err != nil { + if err := checkInputTypes(tx, hf4Active); err != nil { return err } // 4. Output validation. - if err := checkOutputs(tx, hf1Active, hf4Active); err != nil { + if err := checkOutputs(tx, hf4Active); err != nil { return err } @@ -69,55 +65,41 @@ func ValidateTransaction(tx *types.Transaction, txBlob []byte, forks []config.Ha return nil } -func checkInputTypes(tx *types.Transaction, hf1Active, hf4Active bool) error { +func checkInputTypes(tx *types.Transaction, hf4Active bool) error { for _, vin := range tx.Vin { switch vin.(type) { case types.TxInputToKey: // Always valid. case types.TxInputGenesis: - return fmt.Errorf("%w: txin_gen in regular transaction", ErrInvalidInputType) - case types.TxInputHTLC, types.TxInputMultisig: - if !hf1Active { - return fmt.Errorf("%w: tag %d pre-HF1", ErrInvalidInputType, vin.InputType()) - } + return coreerr.E("checkInputTypes", "txin_gen in regular transaction", ErrInvalidInputType) default: - // Future types (ZC, etc.) — accept if HF4+. + // Future types (multisig, HTLC, ZC) — accept if HF4+. if !hf4Active { - return fmt.Errorf("%w: tag %d pre-HF4", ErrInvalidInputType, vin.InputType()) + return coreerr.E("checkInputTypes", fmt.Sprintf("tag %d pre-HF4", vin.InputType()), ErrInvalidInputType) } } } return nil } -func checkOutputs(tx *types.Transaction, hf1Active, hf4Active bool) error { +func checkOutputs(tx *types.Transaction, hf4Active bool) error { if len(tx.Vout) == 0 { return ErrNoOutputs } if hf4Active && uint64(len(tx.Vout)) < config.TxMinAllowedOutputs { - return fmt.Errorf("%w: %d (min %d)", ErrTooFewOutputs, len(tx.Vout), config.TxMinAllowedOutputs) + return coreerr.E("checkOutputs", fmt.Sprintf("%d (min %d)", len(tx.Vout), config.TxMinAllowedOutputs), ErrTooFewOutputs) } if uint64(len(tx.Vout)) > config.TxMaxAllowedOutputs { - return fmt.Errorf("%w: %d", ErrTooManyOutputs, len(tx.Vout)) + return coreerr.E("checkOutputs", fmt.Sprintf("%d", len(tx.Vout)), ErrTooManyOutputs) } for i, vout := range tx.Vout { switch o := vout.(type) { case types.TxOutputBare: if o.Amount == 0 { - return fmt.Errorf("%w: output %d has zero amount", ErrInvalidOutput, i) - } - // Check target type gating. - switch o.Target.(type) { - case types.TxOutToKey: - // Always valid. - case types.TxOutMultisig, types.TxOutHTLC: - if !hf1Active { - return fmt.Errorf("%w: output %d has target type %d pre-HF1", - ErrInvalidOutput, i, o.Target.TargetType()) - } + return coreerr.E("checkOutputs", fmt.Sprintf("output %d has zero amount", i), ErrInvalidOutput) } case types.TxOutputZarcanum: // Validated by proof verification. @@ -127,45 +109,17 @@ func checkOutputs(tx *types.Transaction, hf1Active, hf4Active bool) error { return nil } -// checkTxVersion validates that the transaction version is correct for the -// current hardfork era. After HF5, version must be 3. Before HF5, version 3 -// is rejected. -func checkTxVersion(tx *types.Transaction, forks []config.HardFork, height uint64) error { - hf5Active := config.IsHardForkActive(forks, config.HF5, height) - - if hf5Active { - // After HF5: must be version 3. - if tx.Version != types.VersionPostHF5 { - return fmt.Errorf("%w: got version %d, require %d after HF5", - ErrTxVersionInvalid, tx.Version, types.VersionPostHF5) - } - } else { - // Before HF5: version 3 is not allowed. - if tx.Version >= types.VersionPostHF5 { - return fmt.Errorf("%w: version %d not allowed before HF5", - ErrTxVersionInvalid, tx.Version) - } - } - - return nil -} - func checkKeyImages(tx *types.Transaction) error { seen := make(map[types.KeyImage]struct{}) for _, vin := range tx.Vin { - var ki types.KeyImage - switch v := vin.(type) { - case types.TxInputToKey: - ki = v.KeyImage - case types.TxInputHTLC: - ki = v.KeyImage - default: + toKey, ok := vin.(types.TxInputToKey) + if !ok { continue } - if _, exists := seen[ki]; exists { - return fmt.Errorf("%w: %s", ErrDuplicateKeyImage, ki) + if _, exists := seen[toKey.KeyImage]; exists { + return coreerr.E("checkKeyImages", toKey.KeyImage.String(), ErrDuplicateKeyImage) } - seen[ki] = struct{}{} + seen[toKey.KeyImage] = struct{}{} } return nil } diff --git a/consensus/v2sig.go b/consensus/v2sig.go index 46fae9c..eb91654 100644 --- a/consensus/v2sig.go +++ b/consensus/v2sig.go @@ -9,6 +9,8 @@ import ( "bytes" "fmt" + coreerr "forge.lthn.ai/core/go-log" + "forge.lthn.ai/core/go-blockchain/types" "forge.lthn.ai/core/go-blockchain/wire" ) @@ -38,14 +40,14 @@ func parseV2Signatures(raw []byte) ([]v2SigEntry, error) { dec := wire.NewDecoder(bytes.NewReader(raw)) count := dec.ReadVarint() if dec.Err() != nil { - return nil, fmt.Errorf("read sig count: %w", dec.Err()) + return nil, coreerr.E("parseV2Signatures", "read sig count", dec.Err()) } entries := make([]v2SigEntry, 0, count) for i := uint64(0); i < count; i++ { tag := dec.ReadUint8() if dec.Err() != nil { - return nil, fmt.Errorf("read sig tag %d: %w", i, dec.Err()) + return nil, coreerr.E("parseV2Signatures", fmt.Sprintf("read sig tag %d", i), dec.Err()) } entry := v2SigEntry{tag: tag} @@ -54,7 +56,7 @@ func parseV2Signatures(raw []byte) ([]v2SigEntry, error) { case types.SigTypeZC: zc, err := parseZCSig(dec) if err != nil { - return nil, fmt.Errorf("parse ZC_sig %d: %w", i, err) + return nil, coreerr.E("parseV2Signatures", fmt.Sprintf("parse ZC_sig %d", i), err) } entry.zcSig = zc @@ -74,11 +76,11 @@ func parseV2Signatures(raw []byte) ([]v2SigEntry, error) { skipZarcanumSig(dec) default: - return nil, fmt.Errorf("unsupported sig tag 0x%02x", tag) + return nil, coreerr.E("parseV2Signatures", fmt.Sprintf("unsupported sig tag 0x%02x", tag), nil) } if dec.Err() != nil { - return nil, fmt.Errorf("parse sig %d (tag 0x%02x): %w", i, tag, dec.Err()) + return nil, coreerr.E("parseV2Signatures", fmt.Sprintf("parse sig %d (tag 0x%02x)", i, tag), dec.Err()) } entries = append(entries, entry) } @@ -117,7 +119,7 @@ func parseZCSig(dec *wire.Decoder) (*zcSigData, error) { } if rgCount != rxCount { - return nil, fmt.Errorf("CLSAG r_g count %d != r_x count %d", rgCount, rxCount) + return nil, coreerr.E("parseZCSig", fmt.Sprintf("CLSAG r_g count %d != r_x count %d", rgCount, rxCount), nil) } zc.ringSize = int(rgCount) @@ -155,9 +157,9 @@ func skipZarcanumSig(dec *wire.Decoder) { _ = dec.ReadBytes(32) // CLSAG_GGXXG: c(32) + vec(r_g) + vec(r_x) + K1(32) + K2(32) + K3(32) + K4(32). - _ = dec.ReadBytes(32) // c - skipVecOfPoints(dec) // r_g - skipVecOfPoints(dec) // r_x + _ = dec.ReadBytes(32) // c + skipVecOfPoints(dec) // r_g + skipVecOfPoints(dec) // r_x _ = dec.ReadBytes(128) // K1+K2+K3+K4 } @@ -196,48 +198,48 @@ func parseV2Proofs(raw []byte) (*v2ProofData, error) { dec := wire.NewDecoder(bytes.NewReader(raw)) count := dec.ReadVarint() if dec.Err() != nil { - return nil, fmt.Errorf("read proof count: %w", dec.Err()) + return nil, coreerr.E("parseV2Proofs", "read proof count", dec.Err()) } var data v2ProofData for i := uint64(0); i < count; i++ { tag := dec.ReadUint8() if dec.Err() != nil { - return nil, fmt.Errorf("read proof tag %d: %w", i, dec.Err()) + return nil, coreerr.E("parseV2Proofs", fmt.Sprintf("read proof tag %d", i), dec.Err()) } switch tag { case 46: // zc_asset_surjection_proof: varint(nBGE) + nBGE * BGE_proof nBGE := dec.ReadVarint() if dec.Err() != nil { - return nil, fmt.Errorf("parse BGE count: %w", dec.Err()) + return nil, coreerr.E("parseV2Proofs", "parse BGE count", dec.Err()) } data.bgeProofs = make([][]byte, nBGE) for j := uint64(0); j < nBGE; j++ { data.bgeProofs[j] = readBGEProofBytes(dec) if dec.Err() != nil { - return nil, fmt.Errorf("parse BGE proof %d: %w", j, dec.Err()) + return nil, coreerr.E("parseV2Proofs", fmt.Sprintf("parse BGE proof %d", j), dec.Err()) } } case 47: // zc_outs_range_proof: bpp_serialized + aggregation_proof data.bppProofBytes = readBPPBytes(dec) if dec.Err() != nil { - return nil, fmt.Errorf("parse BPP proof: %w", dec.Err()) + return nil, coreerr.E("parseV2Proofs", "parse BPP proof", dec.Err()) } data.bppCommitments = readAggregationCommitments(dec) if dec.Err() != nil { - return nil, fmt.Errorf("parse aggregation proof: %w", dec.Err()) + return nil, coreerr.E("parseV2Proofs", "parse aggregation proof", dec.Err()) } case 48: // zc_balance_proof: 96 bytes (c, y0, y1) data.balanceProof = dec.ReadBytes(96) if dec.Err() != nil { - return nil, fmt.Errorf("parse balance proof: %w", dec.Err()) + return nil, coreerr.E("parseV2Proofs", "parse balance proof", dec.Err()) } default: - return nil, fmt.Errorf("unsupported proof tag 0x%02x", tag) + return nil, coreerr.E("parseV2Proofs", fmt.Sprintf("unsupported proof tag 0x%02x", tag), nil) } } diff --git a/consensus/verify.go b/consensus/verify.go index 665d704..a7f87d0 100644 --- a/consensus/verify.go +++ b/consensus/verify.go @@ -6,9 +6,10 @@ package consensus import ( - "errors" "fmt" + coreerr "forge.lthn.ai/core/go-log" + "forge.lthn.ai/core/go-blockchain/config" "forge.lthn.ai/core/go-blockchain/crypto" "forge.lthn.ai/core/go-blockchain/types" @@ -58,20 +59,17 @@ func VerifyTransactionSignatures(tx *types.Transaction, forks []config.HardFork, } // verifyV1Signatures checks NLSAG ring signatures for pre-HF4 transactions. -// Both TxInputToKey and TxInputHTLC use NLSAG ring signatures. func verifyV1Signatures(tx *types.Transaction, getRingOutputs RingOutputsFn) error { - // Count ring-sig inputs (TxInputToKey and TxInputHTLC). + // Count key inputs. var keyInputCount int for _, vin := range tx.Vin { - switch vin.(type) { - case types.TxInputToKey, types.TxInputHTLC: + if _, ok := vin.(types.TxInputToKey); ok { keyInputCount++ } } if len(tx.Signatures) != keyInputCount { - return fmt.Errorf("consensus: signature count %d != input count %d", - len(tx.Signatures), keyInputCount) + return coreerr.E("verifyV1Signatures", fmt.Sprintf("consensus: signature count %d != input count %d", len(tx.Signatures), keyInputCount), nil) } // Actual NLSAG verification requires the crypto bridge and ring outputs. @@ -84,40 +82,25 @@ func verifyV1Signatures(tx *types.Transaction, getRingOutputs RingOutputsFn) err var sigIdx int for _, vin := range tx.Vin { - // Extract the common ring-sig fields from either input type. - var amount uint64 - var keyOffsets []types.TxOutRef - var keyImage types.KeyImage - - switch v := vin.(type) { - case types.TxInputToKey: - amount = v.Amount - keyOffsets = v.KeyOffsets - keyImage = v.KeyImage - case types.TxInputHTLC: - amount = v.Amount - keyOffsets = v.KeyOffsets - keyImage = v.KeyImage - default: + inp, ok := vin.(types.TxInputToKey) + if !ok { continue } // Extract absolute global indices from key offsets. - offsets := make([]uint64, len(keyOffsets)) - for i, ref := range keyOffsets { + offsets := make([]uint64, len(inp.KeyOffsets)) + for i, ref := range inp.KeyOffsets { offsets[i] = ref.GlobalIndex } - ringKeys, err := getRingOutputs(amount, offsets) + ringKeys, err := getRingOutputs(inp.Amount, offsets) if err != nil { - return fmt.Errorf("consensus: failed to fetch ring outputs for input %d: %w", - sigIdx, err) + return coreerr.E("verifyV1Signatures", fmt.Sprintf("consensus: failed to fetch ring outputs for input %d", sigIdx), err) } ringSigs := tx.Signatures[sigIdx] if len(ringSigs) != len(ringKeys) { - return fmt.Errorf("consensus: input %d has %d signatures but ring size %d", - sigIdx, len(ringSigs), len(ringKeys)) + return coreerr.E("verifyV1Signatures", fmt.Sprintf("consensus: input %d has %d signatures but ring size %d", sigIdx, len(ringSigs), len(ringKeys)), nil) } // Convert typed slices to raw byte arrays for the crypto bridge. @@ -131,8 +114,8 @@ func verifyV1Signatures(tx *types.Transaction, getRingOutputs RingOutputsFn) err sigs[i] = [64]byte(s) } - if !crypto.CheckRingSignature([32]byte(prefixHash), [32]byte(keyImage), pubs, sigs) { - return fmt.Errorf("consensus: ring signature verification failed for input %d", sigIdx) + if !crypto.CheckRingSignature([32]byte(prefixHash), [32]byte(inp.KeyImage), pubs, sigs) { + return coreerr.E("verifyV1Signatures", fmt.Sprintf("consensus: ring signature verification failed for input %d", sigIdx), nil) } sigIdx++ @@ -146,13 +129,12 @@ func verifyV2Signatures(tx *types.Transaction, getZCRingOutputs ZCRingOutputsFn) // Parse the signature variant vector. sigEntries, err := parseV2Signatures(tx.SignaturesRaw) if err != nil { - return fmt.Errorf("consensus: %w", err) + return coreerr.E("verifyV2Signatures", "consensus", err) } // Match signatures to inputs: each input must have a corresponding signature. if len(sigEntries) != len(tx.Vin) { - return fmt.Errorf("consensus: V2 signature count %d != input count %d", - len(sigEntries), len(tx.Vin)) + return coreerr.E("verifyV2Signatures", fmt.Sprintf("consensus: V2 signature count %d != input count %d", len(sigEntries), len(tx.Vin)), nil) } // Validate that ZC inputs have ZC_sig and vice versa. @@ -160,13 +142,11 @@ func verifyV2Signatures(tx *types.Transaction, getZCRingOutputs ZCRingOutputsFn) switch vin.(type) { case types.TxInputZC: if sigEntries[i].tag != types.SigTypeZC { - return fmt.Errorf("consensus: input %d is ZC but signature tag is 0x%02x", - i, sigEntries[i].tag) + return coreerr.E("verifyV2Signatures", fmt.Sprintf("consensus: input %d is ZC but signature tag is 0x%02x", i, sigEntries[i].tag), nil) } case types.TxInputToKey: if sigEntries[i].tag != types.SigTypeNLSAG && sigEntries[i].tag != types.SigTypeVoid { - return fmt.Errorf("consensus: input %d is to_key but signature tag is 0x%02x", - i, sigEntries[i].tag) + return coreerr.E("verifyV2Signatures", fmt.Sprintf("consensus: input %d is to_key but signature tag is 0x%02x", i, sigEntries[i].tag), nil) } } } @@ -190,7 +170,7 @@ func verifyV2Signatures(tx *types.Transaction, getZCRingOutputs ZCRingOutputsFn) zc := sigEntries[i].zcSig if zc == nil { - return fmt.Errorf("consensus: input %d: missing ZC_sig data", i) + return coreerr.E("verifyV2Signatures", fmt.Sprintf("consensus: input %d: missing ZC_sig data", i), nil) } // Extract absolute global indices from key offsets. @@ -201,12 +181,11 @@ func verifyV2Signatures(tx *types.Transaction, getZCRingOutputs ZCRingOutputsFn) ringMembers, err := getZCRingOutputs(offsets) if err != nil { - return fmt.Errorf("consensus: failed to fetch ZC ring outputs for input %d: %w", i, err) + return coreerr.E("verifyV2Signatures", fmt.Sprintf("consensus: failed to fetch ZC ring outputs for input %d", i), err) } if len(ringMembers) != zc.ringSize { - return fmt.Errorf("consensus: input %d: ring size %d from chain != %d from sig", - i, len(ringMembers), zc.ringSize) + return coreerr.E("verifyV2Signatures", fmt.Sprintf("consensus: input %d: ring size %d from chain != %d from sig", i, len(ringMembers), zc.ringSize), nil) } // Build flat ring: [stealth(32) | commitment(32) | blinded_asset_id(32)] per entry. @@ -225,20 +204,20 @@ func verifyV2Signatures(tx *types.Transaction, getZCRingOutputs ZCRingOutputsFn) [32]byte(zcIn.KeyImage), zc.clsagFlatSig, ) { - return fmt.Errorf("consensus: CLSAG GGX verification failed for input %d", i) + return coreerr.E("verifyV2Signatures", fmt.Sprintf("consensus: CLSAG GGX verification failed for input %d", i), nil) } } // Parse and verify proofs. proofs, err := parseV2Proofs(tx.Proofs) if err != nil { - return fmt.Errorf("consensus: %w", err) + return coreerr.E("verifyV2Signatures", "consensus", err) } // Verify BPP range proof if present. if len(proofs.bppProofBytes) > 0 && len(proofs.bppCommitments) > 0 { if !crypto.VerifyBPP(proofs.bppProofBytes, proofs.bppCommitments) { - return errors.New("consensus: BPP range proof verification failed") + return coreerr.E("verifyV2Signatures", "consensus: BPP range proof verification failed", nil) } } @@ -279,8 +258,7 @@ func verifyBGEProofs(tx *types.Transaction, sigEntries []v2SigEntry, } if len(proofs.bgeProofs) != len(outputAssetIDs) { - return fmt.Errorf("consensus: BGE proof count %d != Zarcanum output count %d", - len(proofs.bgeProofs), len(outputAssetIDs)) + return coreerr.E("verifyBGEProofs", fmt.Sprintf("consensus: BGE proof count %d != Zarcanum output count %d", len(proofs.bgeProofs), len(outputAssetIDs)), nil) } // Collect pseudo-out asset IDs from ZC signatures and expand to full points. @@ -296,7 +274,7 @@ func verifyBGEProofs(tx *types.Transaction, sigEntries []v2SigEntry, for i, p := range pseudoOutAssetIDs { full, err := crypto.PointMul8(p) if err != nil { - return fmt.Errorf("consensus: mul8 pseudo-out asset ID %d: %w", i, err) + return coreerr.E("verifyBGEProofs", fmt.Sprintf("consensus: mul8 pseudo-out asset ID %d", i), err) } mul8PseudoOuts[i] = full } @@ -307,7 +285,7 @@ func verifyBGEProofs(tx *types.Transaction, sigEntries []v2SigEntry, // mul8 the output's blinded asset ID. mul8Out, err := crypto.PointMul8(outAssetID) if err != nil { - return fmt.Errorf("consensus: mul8 output asset ID %d: %w", j, err) + return coreerr.E("verifyBGEProofs", fmt.Sprintf("consensus: mul8 output asset ID %d", j), err) } // ring[i] = mul8(pseudo_out_i) - mul8(output_j) @@ -315,13 +293,13 @@ func verifyBGEProofs(tx *types.Transaction, sigEntries []v2SigEntry, for i, mul8Pseudo := range mul8PseudoOuts { diff, err := crypto.PointSub(mul8Pseudo, mul8Out) if err != nil { - return fmt.Errorf("consensus: BGE ring[%d][%d] sub: %w", j, i, err) + return coreerr.E("verifyBGEProofs", fmt.Sprintf("consensus: BGE ring[%d][%d] sub", j, i), err) } ring[i] = diff } if !crypto.VerifyBGE(context, ring, proofs.bgeProofs[j]) { - return fmt.Errorf("consensus: BGE proof verification failed for output %d", j) + return coreerr.E("verifyBGEProofs", fmt.Sprintf("consensus: BGE proof verification failed for output %d", j), nil) } } diff --git a/mining/miner.go b/mining/miner.go index a1d41f0..f668845 100644 --- a/mining/miner.go +++ b/mining/miner.go @@ -15,6 +15,8 @@ import ( "sync/atomic" "time" + coreerr "forge.lthn.ai/core/go-log" + "forge.lthn.ai/core/go-blockchain/consensus" "forge.lthn.ai/core/go-blockchain/crypto" "forge.lthn.ai/core/go-blockchain/rpc" @@ -139,18 +141,18 @@ func (m *Miner) Start(ctx context.Context) error { // Parse difficulty. diff, err := strconv.ParseUint(tmpl.Difficulty, 10, 64) if err != nil { - return fmt.Errorf("mining: invalid difficulty %q: %w", tmpl.Difficulty, err) + return coreerr.E("Miner.Start", fmt.Sprintf("mining: invalid difficulty %q", tmpl.Difficulty), err) } // Decode the block template blob. blobBytes, err := hex.DecodeString(tmpl.BlockTemplateBlob) if err != nil { - return fmt.Errorf("mining: invalid template blob hex: %w", err) + return coreerr.E("Miner.Start", "mining: invalid template blob hex", err) } dec := wire.NewDecoder(bytes.NewReader(blobBytes)) block := wire.DecodeBlock(dec) if dec.Err() != nil { - return fmt.Errorf("mining: decode template: %w", dec.Err()) + return coreerr.E("Miner.Start", "mining: decode template", dec.Err()) } // Update stats. @@ -202,7 +204,7 @@ func (m *Miner) mine(ctx context.Context, block *types.Block, headerHash [32]byt powHash, err := crypto.RandomXHash(RandomXKey, input[:]) if err != nil { - return fmt.Errorf("mining: RandomX hash: %w", err) + return coreerr.E("Miner.mine", "mining: RandomX hash", err) } m.hashCount.Add(1) @@ -215,12 +217,12 @@ func (m *Miner) mine(ctx context.Context, block *types.Block, headerHash [32]byt enc := wire.NewEncoder(&buf) wire.EncodeBlock(enc, block) if enc.Err() != nil { - return fmt.Errorf("mining: encode solution: %w", enc.Err()) + return coreerr.E("Miner.mine", "mining: encode solution", enc.Err()) } hexBlob := hex.EncodeToString(buf.Bytes()) if err := m.provider.SubmitBlock(hexBlob); err != nil { - return fmt.Errorf("mining: submit block: %w", err) + return coreerr.E("Miner.mine", "mining: submit block", err) } m.blocksFound.Add(1) diff --git a/p2p/sync.go b/p2p/sync.go index 449ad73..f9661f1 100644 --- a/p2p/sync.go +++ b/p2p/sync.go @@ -26,8 +26,8 @@ func (d *CoreSyncData) MarshalSection() levin.Section { "current_height": levin.Uint64Val(d.CurrentHeight), "top_id": levin.StringVal(d.TopID[:]), "last_checkpoint_height": levin.Uint64Val(d.LastCheckpointHeight), - "core_time": levin.Uint64Val(d.CoreTime), - "client_version": levin.StringVal([]byte(d.ClientVersion)), + "core_time": levin.Uint64Val(d.CoreTime), + "client_version": levin.StringVal([]byte(d.ClientVersion)), "non_pruning_mode_enabled": levin.BoolVal(d.NonPruningMode), } } diff --git a/rpc/blocks.go b/rpc/blocks.go index 0a21ad7..10d3003 100644 --- a/rpc/blocks.go +++ b/rpc/blocks.go @@ -5,19 +5,23 @@ package rpc -import "fmt" +import ( + "fmt" + + coreerr "forge.lthn.ai/core/go-log" +) // GetLastBlockHeader returns the header of the most recent block. func (c *Client) GetLastBlockHeader() (*BlockHeader, error) { var resp struct { BlockHeader BlockHeader `json:"block_header"` - Status string `json:"status"` + Status string `json:"status"` } if err := c.call("getlastblockheader", struct{}{}, &resp); err != nil { return nil, err } if resp.Status != "OK" { - return nil, fmt.Errorf("getlastblockheader: status %q", resp.Status) + return nil, coreerr.E("Client.GetLastBlockHeader", fmt.Sprintf("getlastblockheader: status %q", resp.Status), nil) } return &resp.BlockHeader, nil } @@ -29,13 +33,13 @@ func (c *Client) GetBlockHeaderByHeight(height uint64) (*BlockHeader, error) { }{Height: height} var resp struct { BlockHeader BlockHeader `json:"block_header"` - Status string `json:"status"` + Status string `json:"status"` } if err := c.call("getblockheaderbyheight", params, &resp); err != nil { return nil, err } if resp.Status != "OK" { - return nil, fmt.Errorf("getblockheaderbyheight: status %q", resp.Status) + return nil, coreerr.E("Client.GetBlockHeaderByHeight", fmt.Sprintf("getblockheaderbyheight: status %q", resp.Status), nil) } return &resp.BlockHeader, nil } @@ -47,13 +51,13 @@ func (c *Client) GetBlockHeaderByHash(hash string) (*BlockHeader, error) { }{Hash: hash} var resp struct { BlockHeader BlockHeader `json:"block_header"` - Status string `json:"status"` + Status string `json:"status"` } if err := c.call("getblockheaderbyhash", params, &resp); err != nil { return nil, err } if resp.Status != "OK" { - return nil, fmt.Errorf("getblockheaderbyhash: status %q", resp.Status) + return nil, coreerr.E("Client.GetBlockHeaderByHash", fmt.Sprintf("getblockheaderbyhash: status %q", resp.Status), nil) } return &resp.BlockHeader, nil } @@ -73,7 +77,7 @@ func (c *Client) GetBlocksDetails(heightStart, count uint64) ([]BlockDetails, er return nil, err } if resp.Status != "OK" { - return nil, fmt.Errorf("get_blocks_details: status %q", resp.Status) + return nil, coreerr.E("Client.GetBlocksDetails", fmt.Sprintf("get_blocks_details: status %q", resp.Status), nil) } return resp.Blocks, nil } diff --git a/rpc/client.go b/rpc/client.go index 452467d..e66c2a7 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -14,6 +14,8 @@ import ( "net/http" "net/url" "time" + + coreerr "forge.lthn.ai/core/go-log" ) // Client is a Lethean daemon RPC client. @@ -66,10 +68,10 @@ type jsonRPCRequest struct { } type jsonRPCResponse struct { - JSONRPC string `json:"jsonrpc"` - ID json.RawMessage `json:"id"` - Result json.RawMessage `json:"result"` - Error *jsonRPCError `json:"error,omitempty"` + JSONRPC string `json:"jsonrpc"` + ID json.RawMessage `json:"id"` + Result json.RawMessage `json:"result"` + Error *jsonRPCError `json:"error,omitempty"` } type jsonRPCError struct { @@ -86,27 +88,27 @@ func (c *Client) call(method string, params any, result any) error { Params: params, }) if err != nil { - return fmt.Errorf("marshal request: %w", err) + return coreerr.E("Client.call", "marshal request", err) } resp, err := c.httpClient.Post(c.url, "application/json", bytes.NewReader(reqBody)) if err != nil { - return fmt.Errorf("post %s: %w", method, err) + return coreerr.E("Client.call", fmt.Sprintf("post %s", method), err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return fmt.Errorf("http %d from %s", resp.StatusCode, method) + return coreerr.E("Client.call", fmt.Sprintf("http %d from %s", resp.StatusCode, method), nil) } body, err := io.ReadAll(resp.Body) if err != nil { - return fmt.Errorf("read response: %w", err) + return coreerr.E("Client.call", "read response", err) } var rpcResp jsonRPCResponse if err := json.Unmarshal(body, &rpcResp); err != nil { - return fmt.Errorf("unmarshal response: %w", err) + return coreerr.E("Client.call", "unmarshal response", err) } if rpcResp.Error != nil { @@ -115,7 +117,7 @@ func (c *Client) call(method string, params any, result any) error { if result != nil && len(rpcResp.Result) > 0 { if err := json.Unmarshal(rpcResp.Result, result); err != nil { - return fmt.Errorf("unmarshal result: %w", err) + return coreerr.E("Client.call", "unmarshal result", err) } } return nil @@ -125,28 +127,28 @@ func (c *Client) call(method string, params any, result any) error { func (c *Client) legacyCall(path string, params any, result any) error { reqBody, err := json.Marshal(params) if err != nil { - return fmt.Errorf("marshal request: %w", err) + return coreerr.E("Client.legacyCall", "marshal request", err) } url := c.baseURL + path resp, err := c.httpClient.Post(url, "application/json", bytes.NewReader(reqBody)) if err != nil { - return fmt.Errorf("post %s: %w", path, err) + return coreerr.E("Client.legacyCall", fmt.Sprintf("post %s", path), err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return fmt.Errorf("http %d from %s", resp.StatusCode, path) + return coreerr.E("Client.legacyCall", fmt.Sprintf("http %d from %s", resp.StatusCode, path), nil) } body, err := io.ReadAll(resp.Body) if err != nil { - return fmt.Errorf("read response: %w", err) + return coreerr.E("Client.legacyCall", "read response", err) } if result != nil { if err := json.Unmarshal(body, result); err != nil { - return fmt.Errorf("unmarshal response: %w", err) + return coreerr.E("Client.legacyCall", "unmarshal response", err) } } return nil diff --git a/rpc/info.go b/rpc/info.go index c3be527..2c6a152 100644 --- a/rpc/info.go +++ b/rpc/info.go @@ -5,7 +5,11 @@ package rpc -import "fmt" +import ( + "fmt" + + coreerr "forge.lthn.ai/core/go-log" +) // GetInfo returns the daemon status. // Uses flags=0 for the cheapest query (no expensive calculations). @@ -21,7 +25,7 @@ func (c *Client) GetInfo() (*DaemonInfo, error) { return nil, err } if resp.Status != "OK" { - return nil, fmt.Errorf("getinfo: status %q", resp.Status) + return nil, coreerr.E("Client.GetInfo", fmt.Sprintf("getinfo: status %q", resp.Status), nil) } return &resp.DaemonInfo, nil } @@ -37,7 +41,7 @@ func (c *Client) GetHeight() (uint64, error) { return 0, err } if resp.Status != "OK" { - return 0, fmt.Errorf("getheight: status %q", resp.Status) + return 0, coreerr.E("Client.GetHeight", fmt.Sprintf("getheight: status %q", resp.Status), nil) } return resp.Height, nil } @@ -52,7 +56,7 @@ func (c *Client) GetBlockCount() (uint64, error) { return 0, err } if resp.Status != "OK" { - return 0, fmt.Errorf("getblockcount: status %q", resp.Status) + return 0, coreerr.E("Client.GetBlockCount", fmt.Sprintf("getblockcount: status %q", resp.Status), nil) } return resp.Count, nil } diff --git a/rpc/mining.go b/rpc/mining.go index 9da2f8c..68861da 100644 --- a/rpc/mining.go +++ b/rpc/mining.go @@ -5,7 +5,11 @@ package rpc -import "fmt" +import ( + "fmt" + + coreerr "forge.lthn.ai/core/go-log" +) // SubmitBlock submits a mined block to the daemon. // The hexBlob is the hex-encoded serialised block. @@ -20,7 +24,7 @@ func (c *Client) SubmitBlock(hexBlob string) error { return err } if resp.Status != "OK" { - return fmt.Errorf("submitblock: status %q", resp.Status) + return coreerr.E("Client.SubmitBlock", fmt.Sprintf("submitblock: status %q", resp.Status), nil) } return nil } @@ -47,7 +51,7 @@ func (c *Client) GetBlockTemplate(walletAddr string) (*BlockTemplateResponse, er return nil, err } if resp.Status != "OK" { - return nil, fmt.Errorf("getblocktemplate: status %q", resp.Status) + return nil, coreerr.E("Client.GetBlockTemplate", fmt.Sprintf("getblocktemplate: status %q", resp.Status), nil) } return &resp, nil } diff --git a/rpc/transactions.go b/rpc/transactions.go index fa5aca7..652476e 100644 --- a/rpc/transactions.go +++ b/rpc/transactions.go @@ -5,7 +5,11 @@ package rpc -import "fmt" +import ( + "fmt" + + coreerr "forge.lthn.ai/core/go-log" +) // GetTxDetails returns detailed information about a transaction. func (c *Client) GetTxDetails(txHash string) (*TxInfo, error) { @@ -20,7 +24,7 @@ func (c *Client) GetTxDetails(txHash string) (*TxInfo, error) { return nil, err } if resp.Status != "OK" { - return nil, fmt.Errorf("get_tx_details: status %q", resp.Status) + return nil, coreerr.E("Client.GetTxDetails", fmt.Sprintf("get_tx_details: status %q", resp.Status), nil) } return &resp.TxInfo, nil } @@ -41,7 +45,7 @@ func (c *Client) GetTransactions(hashes []string) (txsHex []string, missed []str return nil, nil, err } if resp.Status != "OK" { - return nil, nil, fmt.Errorf("gettransactions: status %q", resp.Status) + return nil, nil, coreerr.E("Client.GetTransactions", fmt.Sprintf("gettransactions: status %q", resp.Status), nil) } return resp.TxsAsHex, resp.MissedTx, nil } diff --git a/rpc/types.go b/rpc/types.go index 9567839..0f5d97c 100644 --- a/rpc/types.go +++ b/rpc/types.go @@ -48,22 +48,22 @@ type DaemonInfo struct { // BlockDetails is a full block with metadata as returned by get_blocks_details. type BlockDetails struct { - Height uint64 `json:"height"` - Timestamp uint64 `json:"timestamp"` - ActualTimestamp uint64 `json:"actual_timestamp"` - BaseReward uint64 `json:"base_reward"` - SummaryReward uint64 `json:"summary_reward"` - TotalFee uint64 `json:"total_fee"` - ID string `json:"id"` - PrevID string `json:"prev_id"` + Height uint64 `json:"height"` + Timestamp uint64 `json:"timestamp"` + ActualTimestamp uint64 `json:"actual_timestamp"` + BaseReward uint64 `json:"base_reward"` + SummaryReward uint64 `json:"summary_reward"` + TotalFee uint64 `json:"total_fee"` + ID string `json:"id"` + PrevID string `json:"prev_id"` Difficulty string `json:"difficulty"` - CumulativeDiffPrecise string `json:"cumulative_diff_precise"` - Type uint64 `json:"type"` - IsOrphan bool `json:"is_orphan"` - CumulativeSize uint64 `json:"block_cumulative_size"` - Blob string `json:"blob"` - ObjectInJSON string `json:"object_in_json"` - Transactions []TxInfo `json:"transactions_details"` + CumulativeDiffPrecise string `json:"cumulative_diff_precise"` + Type uint64 `json:"type"` + IsOrphan bool `json:"is_orphan"` + CumulativeSize uint64 `json:"block_cumulative_size"` + Blob string `json:"blob"` + ObjectInJSON string `json:"object_in_json"` + Transactions []TxInfo `json:"transactions_details"` } // TxInfo is transaction metadata as returned by get_tx_details. diff --git a/rpc/wallet.go b/rpc/wallet.go index 4ae6c78..b5c130b 100644 --- a/rpc/wallet.go +++ b/rpc/wallet.go @@ -8,12 +8,14 @@ package rpc import ( "encoding/hex" "fmt" + + coreerr "forge.lthn.ai/core/go-log" ) // RandomOutputEntry is a decoy output returned by getrandom_outs. type RandomOutputEntry struct { GlobalIndex uint64 `json:"global_index"` - PublicKey string `json:"public_key"` + PublicKey string `json:"public_key"` } // GetRandomOutputs fetches random decoy outputs for ring construction. @@ -33,7 +35,7 @@ func (c *Client) GetRandomOutputs(amount uint64, count int) ([]RandomOutputEntry return nil, err } if resp.Status != "OK" { - return nil, fmt.Errorf("getrandom_outs: status %q", resp.Status) + return nil, coreerr.E("Client.GetRandomOutputs", fmt.Sprintf("getrandom_outs: status %q", resp.Status), nil) } return resp.Outs, nil } @@ -53,7 +55,7 @@ func (c *Client) SendRawTransaction(txBlob []byte) error { return err } if resp.Status != "OK" { - return fmt.Errorf("sendrawtransaction: status %q", resp.Status) + return coreerr.E("Client.SendRawTransaction", fmt.Sprintf("sendrawtransaction: status %q", resp.Status), nil) } return nil } diff --git a/sync_service.go b/sync_service.go index 7e51bf2..d929f94 100644 --- a/sync_service.go +++ b/sync_service.go @@ -14,6 +14,8 @@ import ( "net" "time" + coreerr "forge.lthn.ai/core/go-log" + "forge.lthn.ai/core/go-blockchain/chain" "forge.lthn.ai/core/go-blockchain/config" "forge.lthn.ai/core/go-blockchain/p2p" @@ -54,7 +56,7 @@ func syncLoop(ctx context.Context, c *chain.Chain, cfg *config.ChainConfig, fork func syncOnce(ctx context.Context, c *chain.Chain, cfg *config.ChainConfig, opts chain.SyncOptions, seed string) error { conn, err := net.DialTimeout("tcp", seed, 10*time.Second) if err != nil { - return fmt.Errorf("dial %s: %w", seed, err) + return coreerr.E("syncOnce", fmt.Sprintf("dial %s", seed), err) } defer conn.Close() @@ -81,23 +83,23 @@ func syncOnce(ctx context.Context, c *chain.Chain, cfg *config.ChainConfig, opts } payload, err := p2p.EncodeHandshakeRequest(&req) if err != nil { - return fmt.Errorf("encode handshake: %w", err) + return coreerr.E("syncOnce", "encode handshake", err) } if err := lc.WritePacket(p2p.CommandHandshake, payload, true); err != nil { - return fmt.Errorf("write handshake: %w", err) + return coreerr.E("syncOnce", "write handshake", err) } hdr, data, err := lc.ReadPacket() if err != nil { - return fmt.Errorf("read handshake: %w", err) + return coreerr.E("syncOnce", "read handshake", err) } if hdr.Command != uint32(p2p.CommandHandshake) { - return fmt.Errorf("unexpected command %d", hdr.Command) + return coreerr.E("syncOnce", fmt.Sprintf("unexpected command %d", hdr.Command), nil) } var resp p2p.HandshakeResponse if err := resp.Decode(data); err != nil { - return fmt.Errorf("decode handshake: %w", err) + return coreerr.E("syncOnce", "decode handshake", err) } localSync := p2p.CoreSyncData{ diff --git a/tui/explorer_model.go b/tui/explorer_model.go index 4120188..c1af673 100644 --- a/tui/explorer_model.go +++ b/tui/explorer_model.go @@ -10,8 +10,8 @@ import ( "strings" "time" - tea "github.com/charmbracelet/bubbletea" cli "forge.lthn.ai/core/cli/pkg/cli" + tea "github.com/charmbracelet/bubbletea" "forge.lthn.ai/core/go-blockchain/chain" "forge.lthn.ai/core/go-blockchain/types" @@ -23,7 +23,7 @@ var _ cli.FrameModel = (*ExplorerModel)(nil) type explorerView int const ( - viewBlockList explorerView = iota + viewBlockList explorerView = iota viewBlockDetail viewTxDetail ) diff --git a/types/address.go b/types/address.go index d9a2f25..053c8cc 100644 --- a/types/address.go +++ b/types/address.go @@ -10,10 +10,11 @@ package types import ( - "errors" "fmt" "math/big" + coreerr "forge.lthn.ai/core/go-log" + "golang.org/x/crypto/sha3" "forge.lthn.ai/core/go-blockchain/config" @@ -81,24 +82,24 @@ func (a *Address) Encode(prefix uint64) string { func DecodeAddress(s string) (*Address, uint64, error) { raw, err := base58Decode(s) if err != nil { - return nil, 0, fmt.Errorf("types: base58 decode failed: %w", err) + return nil, 0, coreerr.E("DecodeAddress", "types: base58 decode failed", err) } // The minimum size is: 1 byte prefix varint + 32 + 32 + 1 flags + 4 checksum = 70. if len(raw) < 70 { - return nil, 0, errors.New("types: address data too short") + return nil, 0, coreerr.E("DecodeAddress", "types: address data too short", nil) } // Decode the prefix varint. prefix, prefixLen, err := decodeVarint(raw) if err != nil { - return nil, 0, fmt.Errorf("types: invalid address prefix varint: %w", err) + return nil, 0, coreerr.E("DecodeAddress", "types: invalid address prefix varint", err) } // After the prefix we need exactly 32+32+1+4 = 69 bytes. remaining := raw[prefixLen:] if len(remaining) != 69 { - return nil, 0, fmt.Errorf("types: unexpected address data length: want 69 bytes after prefix, got %d", len(remaining)) + return nil, 0, coreerr.E("DecodeAddress", fmt.Sprintf("types: unexpected address data length: want 69 bytes after prefix, got %d", len(remaining)), nil) } // Validate checksum: Keccak-256 of everything except the last 4 bytes. @@ -109,7 +110,7 @@ func DecodeAddress(s string) (*Address, uint64, error) { expectedChecksum[1] != actualChecksum[1] || expectedChecksum[2] != actualChecksum[2] || expectedChecksum[3] != actualChecksum[3] { - return nil, 0, errors.New("types: address checksum mismatch") + return nil, 0, coreerr.E("DecodeAddress", "types: address checksum mismatch", nil) } addr := &Address{} @@ -214,7 +215,7 @@ func encodeBlock(block []byte, encodedSize int) []byte { // base58Decode decodes a CryptoNote base58 string back into raw bytes. func base58Decode(s string) ([]byte, error) { if len(s) == 0 { - return nil, errors.New("types: empty base58 string") + return nil, coreerr.E("base58Decode", "types: empty base58 string", nil) } fullBlocks := len(s) / 11 @@ -222,7 +223,7 @@ func base58Decode(s string) ([]byte, error) { // Validate that the last block size maps to a valid byte count. if lastBlockChars > 0 && base58ReverseBlockSizes[lastBlockChars] < 0 { - return nil, fmt.Errorf("types: invalid base58 string length %d", len(s)) + return nil, coreerr.E("base58Decode", fmt.Sprintf("types: invalid base58 string length %d", len(s)), nil) } var result []byte @@ -257,7 +258,7 @@ func decodeBlock(s string, byteCount int) ([]byte, error) { for _, c := range []byte(s) { idx := base58CharIndex(c) if idx < 0 { - return nil, fmt.Errorf("types: invalid base58 character %q", c) + return nil, coreerr.E("decodeBlock", fmt.Sprintf("types: invalid base58 character %q", c), nil) } num.Mul(num, base) num.Add(num, big.NewInt(int64(idx))) @@ -266,7 +267,7 @@ func decodeBlock(s string, byteCount int) ([]byte, error) { // Convert to fixed-size byte array, big-endian. raw := num.Bytes() if len(raw) > byteCount { - return nil, fmt.Errorf("types: base58 block overflow: decoded %d bytes, expected %d", len(raw), byteCount) + return nil, coreerr.E("decodeBlock", fmt.Sprintf("types: base58 block overflow: decoded %d bytes, expected %d", len(raw), byteCount), nil) } // Pad with leading zeroes if necessary. @@ -310,7 +311,7 @@ func encodeVarint(v uint64) []byte { func decodeVarint(data []byte) (uint64, int, error) { if len(data) == 0 { - return 0, 0, errors.New("types: cannot decode varint from empty data") + return 0, 0, coreerr.E("decodeVarint", "types: cannot decode varint from empty data", nil) } var v uint64 for i := 0; i < len(data) && i < 10; i++ { @@ -319,5 +320,5 @@ func decodeVarint(data []byte) (uint64, int, error) { return v, i + 1, nil } } - return 0, 0, errors.New("types: varint overflow") + return 0, 0, coreerr.E("decodeVarint", "types: varint overflow", nil) } diff --git a/types/types.go b/types/types.go index 652c1f0..4d85fee 100644 --- a/types/types.go +++ b/types/types.go @@ -15,6 +15,8 @@ package types import ( "encoding/hex" "fmt" + + coreerr "forge.lthn.ai/core/go-log" ) // Hash is a 256-bit (32-byte) hash value, typically produced by Keccak-256. @@ -40,10 +42,10 @@ func HashFromHex(s string) (Hash, error) { var h Hash b, err := hex.DecodeString(s) if err != nil { - return h, fmt.Errorf("types: invalid hex for hash: %w", err) + return h, coreerr.E("HashFromHex", "types: invalid hex for hash", err) } if len(b) != 32 { - return h, fmt.Errorf("types: hash hex must be 64 characters, got %d", len(s)) + return h, coreerr.E("HashFromHex", fmt.Sprintf("types: hash hex must be 64 characters, got %d", len(s)), nil) } copy(h[:], b) return h, nil @@ -65,10 +67,10 @@ func PublicKeyFromHex(s string) (PublicKey, error) { var pk PublicKey b, err := hex.DecodeString(s) if err != nil { - return pk, fmt.Errorf("types: invalid hex for public key: %w", err) + return pk, coreerr.E("PublicKeyFromHex", "types: invalid hex for public key", err) } if len(b) != 32 { - return pk, fmt.Errorf("types: public key hex must be 64 characters, got %d", len(s)) + return pk, coreerr.E("PublicKeyFromHex", fmt.Sprintf("types: public key hex must be 64 characters, got %d", len(s)), nil) } copy(pk[:], b) return pk, nil diff --git a/wallet/account.go b/wallet/account.go index f5be1eb..844cd53 100644 --- a/wallet/account.go +++ b/wallet/account.go @@ -15,10 +15,10 @@ import ( "crypto/rand" "encoding/hex" "encoding/json" - "errors" - "fmt" "io" + coreerr "forge.lthn.ai/core/go-log" + "golang.org/x/crypto/argon2" store "forge.lthn.ai/core/go-store" @@ -64,7 +64,7 @@ type Account struct { func GenerateAccount() (*Account, error) { spendPub, spendSec, err := crypto.GenerateKeys() if err != nil { - return nil, fmt.Errorf("wallet: generate spend keys: %w", err) + return nil, coreerr.E("GenerateAccount", "wallet: generate spend keys", err) } return accountFromSpendKey(spendSec, spendPub) } @@ -74,11 +74,11 @@ func GenerateAccount() (*Account, error) { func RestoreFromSeed(phrase string) (*Account, error) { key, err := MnemonicDecode(phrase) if err != nil { - return nil, fmt.Errorf("wallet: restore from seed: %w", err) + return nil, coreerr.E("RestoreFromSeed", "wallet: restore from seed", err) } spendPub, err := crypto.SecretToPublic(key) if err != nil { - return nil, fmt.Errorf("wallet: spend pub from secret: %w", err) + return nil, coreerr.E("RestoreFromSeed", "wallet: spend pub from secret", err) } return accountFromSpendKey(key, spendPub) } @@ -88,7 +88,7 @@ func RestoreFromSeed(phrase string) (*Account, error) { func RestoreViewOnly(viewSecret types.SecretKey, spendPublic types.PublicKey) (*Account, error) { viewPub, err := crypto.SecretToPublic([32]byte(viewSecret)) if err != nil { - return nil, fmt.Errorf("wallet: view pub from secret: %w", err) + return nil, coreerr.E("RestoreViewOnly", "wallet: view pub from secret", err) } return &Account{ SpendPublicKey: spendPublic, @@ -115,28 +115,28 @@ func (a *Account) Address() types.Address { func (a *Account) Save(s *store.Store, password string) error { plaintext, err := json.Marshal(a) if err != nil { - return fmt.Errorf("wallet: marshal account: %w", err) + return coreerr.E("Account.Save", "wallet: marshal account", err) } salt := make([]byte, saltLen) if _, err := io.ReadFull(rand.Reader, salt); err != nil { - return fmt.Errorf("wallet: generate salt: %w", err) + return coreerr.E("Account.Save", "wallet: generate salt", err) } derived := argon2.IDKey([]byte(password), salt, argonTime, argonMemory, argonThreads, argonKeyLen) block, err := aes.NewCipher(derived) if err != nil { - return fmt.Errorf("wallet: aes cipher: %w", err) + return coreerr.E("Account.Save", "wallet: aes cipher", err) } gcm, err := cipher.NewGCM(block) if err != nil { - return fmt.Errorf("wallet: gcm: %w", err) + return coreerr.E("Account.Save", "wallet: gcm", err) } nonce := make([]byte, nonceLen) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { - return fmt.Errorf("wallet: generate nonce: %w", err) + return coreerr.E("Account.Save", "wallet: generate nonce", err) } ciphertext := gcm.Seal(nil, nonce, plaintext, nil) @@ -154,16 +154,16 @@ func (a *Account) Save(s *store.Store, password string) error { func LoadAccount(s *store.Store, password string) (*Account, error) { encoded, err := s.Get(groupAccount, keyAccount) if err != nil { - return nil, fmt.Errorf("wallet: load account: %w", err) + return nil, coreerr.E("LoadAccount", "wallet: load account", err) } blob, err := hex.DecodeString(encoded) if err != nil { - return nil, fmt.Errorf("wallet: decode account hex: %w", err) + return nil, coreerr.E("LoadAccount", "wallet: decode account hex", err) } if len(blob) < saltLen+nonceLen+1 { - return nil, errors.New("wallet: account data too short") + return nil, coreerr.E("LoadAccount", "wallet: account data too short", nil) } salt := blob[:saltLen] @@ -174,21 +174,21 @@ func LoadAccount(s *store.Store, password string) (*Account, error) { block, err := aes.NewCipher(derived) if err != nil { - return nil, fmt.Errorf("wallet: aes cipher: %w", err) + return nil, coreerr.E("LoadAccount", "wallet: aes cipher", err) } gcm, err := cipher.NewGCM(block) if err != nil { - return nil, fmt.Errorf("wallet: gcm: %w", err) + return nil, coreerr.E("LoadAccount", "wallet: gcm", err) } plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) if err != nil { - return nil, fmt.Errorf("wallet: decrypt account: %w", err) + return nil, coreerr.E("LoadAccount", "wallet: decrypt account", err) } var acc Account if err := json.Unmarshal(plaintext, &acc); err != nil { - return nil, fmt.Errorf("wallet: unmarshal account: %w", err) + return nil, coreerr.E("LoadAccount", "wallet: unmarshal account", err) } return &acc, nil } @@ -201,7 +201,7 @@ func accountFromSpendKey(spendSec, spendPub [32]byte) (*Account, error) { crypto.ScReduce32(&viewSec) viewPub, err := crypto.SecretToPublic(viewSec) if err != nil { - return nil, fmt.Errorf("wallet: view pub from secret: %w", err) + return nil, coreerr.E("accountFromSpendKey", "wallet: view pub from secret", err) } return &Account{ SpendPublicKey: types.PublicKey(spendPub), diff --git a/wallet/builder.go b/wallet/builder.go index 52532a7..67a0566 100644 --- a/wallet/builder.go +++ b/wallet/builder.go @@ -12,10 +12,11 @@ package wallet import ( "bytes" "cmp" - "errors" "fmt" "slices" + coreerr "forge.lthn.ai/core/go-log" + "forge.lthn.ai/core/go-blockchain/config" "forge.lthn.ai/core/go-blockchain/crypto" "forge.lthn.ai/core/go-blockchain/types" @@ -82,15 +83,14 @@ func (b *V1Builder) Build(req *BuildRequest) (*types.Transaction, error) { destTotal += dst.Amount } if sourceTotal < destTotal+req.Fee { - return nil, fmt.Errorf("wallet: insufficient funds: have %d, need %d", - sourceTotal, destTotal+req.Fee) + return nil, coreerr.E("V1Builder.Build", fmt.Sprintf("wallet: insufficient funds: have %d, need %d", sourceTotal, destTotal+req.Fee), nil) } change := sourceTotal - destTotal - req.Fee // 2. Generate one-time TX key pair. txPub, txSec, err := crypto.GenerateKeys() if err != nil { - return nil, fmt.Errorf("wallet: generate tx keys: %w", err) + return nil, coreerr.E("V1Builder.Build", "wallet: generate tx keys", err) } tx := &types.Transaction{Version: types.VersionPreHF4} @@ -101,7 +101,7 @@ func (b *V1Builder) Build(req *BuildRequest) (*types.Transaction, error) { for i, src := range req.Sources { input, meta, buildErr := b.buildInput(&src) if buildErr != nil { - return nil, fmt.Errorf("wallet: input %d: %w", i, buildErr) + return nil, coreerr.E("V1Builder.Build", fmt.Sprintf("wallet: input %d", i), buildErr) } tx.Vin = append(tx.Vin, input) metas[i] = meta @@ -112,7 +112,7 @@ func (b *V1Builder) Build(req *BuildRequest) (*types.Transaction, error) { for _, dst := range req.Destinations { out, outErr := deriveOutput(txSec, dst.Address, outputIdx, dst.Amount) if outErr != nil { - return nil, fmt.Errorf("wallet: output %d: %w", outputIdx, outErr) + return nil, coreerr.E("V1Builder.Build", fmt.Sprintf("wallet: output %d", outputIdx), outErr) } tx.Vout = append(tx.Vout, out) outputIdx++ @@ -122,7 +122,7 @@ func (b *V1Builder) Build(req *BuildRequest) (*types.Transaction, error) { if change > 0 { out, outErr := deriveOutput(txSec, req.SenderAddress, outputIdx, change) if outErr != nil { - return nil, fmt.Errorf("wallet: change output: %w", outErr) + return nil, coreerr.E("V1Builder.Build", "wallet: change output", outErr) } tx.Vout = append(tx.Vout, out) } @@ -136,7 +136,7 @@ func (b *V1Builder) Build(req *BuildRequest) (*types.Transaction, error) { for i, meta := range metas { sigs, signErr := b.signer.SignInput(prefixHash, meta.ephemeral, meta.ring, meta.realIndex) if signErr != nil { - return nil, fmt.Errorf("wallet: sign input %d: %w", i, signErr) + return nil, coreerr.E("V1Builder.Build", fmt.Sprintf("wallet: sign input %d", i), signErr) } tx.Signatures = append(tx.Signatures, sigs) } @@ -168,7 +168,7 @@ func (b *V1Builder) buildInput(src *Transfer) (types.TxInputToKey, inputMeta, er return m.GlobalIndex == src.GlobalIndex }) if realIdx < 0 { - return types.TxInputToKey{}, inputMeta{}, errors.New("real output not found in ring") + return types.TxInputToKey{}, inputMeta{}, coreerr.E("V1Builder.buildInput", "real output not found in ring", nil) } // Build key offsets and public key list. @@ -203,13 +203,13 @@ func deriveOutput(txSec [32]byte, addr types.Address, index uint64, amount uint6 derivation, err := crypto.GenerateKeyDerivation( [32]byte(addr.ViewPublicKey), txSec) if err != nil { - return types.TxOutputBare{}, fmt.Errorf("key derivation: %w", err) + return types.TxOutputBare{}, coreerr.E("deriveOutput", "key derivation", err) } ephPub, err := crypto.DerivePublicKey( derivation, index, [32]byte(addr.SpendPublicKey)) if err != nil { - return types.TxOutputBare{}, fmt.Errorf("derive public key: %w", err) + return types.TxOutputBare{}, coreerr.E("deriveOutput", "derive public key", err) } return types.TxOutputBare{ @@ -224,7 +224,7 @@ func SerializeTransaction(tx *types.Transaction) ([]byte, error) { enc := wire.NewEncoder(&buf) wire.EncodeTransaction(enc, tx) if err := enc.Err(); err != nil { - return nil, fmt.Errorf("wallet: encode tx: %w", err) + return nil, coreerr.E("SerializeTransaction", "wallet: encode tx", err) } return buf.Bytes(), nil } diff --git a/wallet/extra.go b/wallet/extra.go index 63ae790..9dd8552 100644 --- a/wallet/extra.go +++ b/wallet/extra.go @@ -13,6 +13,8 @@ import ( "encoding/binary" "fmt" + coreerr "forge.lthn.ai/core/go-log" + "forge.lthn.ai/core/go-blockchain/types" "forge.lthn.ai/core/go-blockchain/wire" ) @@ -46,7 +48,7 @@ func ParseTxExtra(raw []byte) (*TxExtra, error) { count, n, err := wire.DecodeVarint(raw) if err != nil { - return extra, fmt.Errorf("wallet: extra: invalid varint count: %w", err) + return extra, coreerr.E("ParseTxExtra", "wallet: extra: invalid varint count", err) } pos := n @@ -57,7 +59,7 @@ func ParseTxExtra(raw []byte) (*TxExtra, error) { switch tag { case extraTagPublicKey: if pos+32 > len(raw) { - return extra, fmt.Errorf("wallet: extra: truncated public key at offset %d", pos) + return extra, coreerr.E("ParseTxExtra", fmt.Sprintf("wallet: extra: truncated public key at offset %d", pos), nil) } copy(extra.TxPublicKey[:], raw[pos:pos+32]) pos += 32 @@ -65,7 +67,7 @@ func ParseTxExtra(raw []byte) (*TxExtra, error) { case extraTagUnlockTime: val, vn, vErr := wire.DecodeVarint(raw[pos:]) if vErr != nil { - return extra, fmt.Errorf("wallet: extra: invalid unlock_time varint: %w", vErr) + return extra, coreerr.E("ParseTxExtra", "wallet: extra: invalid unlock_time varint", vErr) } extra.UnlockTime = val pos += vn @@ -73,7 +75,7 @@ func ParseTxExtra(raw []byte) (*TxExtra, error) { case extraTagDerivationHint: length, vn, vErr := wire.DecodeVarint(raw[pos:]) if vErr != nil { - return extra, fmt.Errorf("wallet: extra: invalid hint length varint: %w", vErr) + return extra, coreerr.E("ParseTxExtra", "wallet: extra: invalid hint length varint", vErr) } pos += vn if length == 2 && pos+2 <= len(raw) { @@ -110,11 +112,11 @@ func skipExtraElement(data []byte, tag uint8) (int, error) { // String types: varint(length) + length bytes. case 7, 9, 11, 19: if len(data) == 0 { - return 0, fmt.Errorf("wallet: extra: no data for string tag %d", tag) + return 0, coreerr.E("skipExtraElement", fmt.Sprintf("wallet: extra: no data for string tag %d", tag), nil) } length, n, err := wire.DecodeVarint(data) if err != nil { - return 0, fmt.Errorf("wallet: extra: invalid string length for tag %d: %w", tag, err) + return 0, coreerr.E("skipExtraElement", fmt.Sprintf("wallet: extra: invalid string length for tag %d", tag), err) } return n + int(length), nil @@ -122,7 +124,7 @@ func skipExtraElement(data []byte, tag uint8) (int, error) { case 14, 15, 16, 26, 27: _, n, err := wire.DecodeVarint(data) if err != nil { - return 0, fmt.Errorf("wallet: extra: invalid varint for tag %d: %w", tag, err) + return 0, coreerr.E("skipExtraElement", fmt.Sprintf("wallet: extra: invalid varint for tag %d", tag), err) } return n, nil @@ -139,6 +141,6 @@ func skipExtraElement(data []byte, tag uint8) (int, error) { return 64, nil // signature default: - return 0, fmt.Errorf("wallet: extra: unknown tag %d", tag) + return 0, coreerr.E("skipExtraElement", fmt.Sprintf("wallet: extra: unknown tag %d", tag), nil) } } diff --git a/wallet/mnemonic.go b/wallet/mnemonic.go index 2bc1fff..164b26f 100644 --- a/wallet/mnemonic.go +++ b/wallet/mnemonic.go @@ -2,10 +2,11 @@ package wallet import ( "encoding/binary" - "errors" "fmt" "hash/crc32" "strings" + + coreerr "forge.lthn.ai/core/go-log" ) const numWords = 1626 @@ -13,7 +14,7 @@ const numWords = 1626 // MnemonicEncode converts a 32-byte secret key to a 25-word mnemonic phrase. func MnemonicEncode(key []byte) (string, error) { if len(key) != 32 { - return "", fmt.Errorf("wallet: mnemonic encode requires 32 bytes, got %d", len(key)) + return "", coreerr.E("MnemonicEncode", fmt.Sprintf("wallet: mnemonic encode requires 32 bytes, got %d", len(key)), nil) } words := make([]string, 0, 25) @@ -39,12 +40,12 @@ func MnemonicDecode(phrase string) ([32]byte, error) { words := strings.Fields(phrase) if len(words) != 25 { - return key, fmt.Errorf("wallet: mnemonic requires 25 words, got %d", len(words)) + return key, coreerr.E("MnemonicDecode", fmt.Sprintf("wallet: mnemonic requires 25 words, got %d", len(words)), nil) } expected := checksumIndex(words[:24]) if words[24] != words[expected] { - return key, errors.New("wallet: mnemonic checksum failed") + return key, coreerr.E("MnemonicDecode", "wallet: mnemonic checksum failed", nil) } n := uint32(numWords) @@ -61,7 +62,7 @@ func MnemonicDecode(phrase string) ([32]byte, error) { if !ok3 { word = words[i*3+2] } - return key, fmt.Errorf("wallet: unknown mnemonic word %q", word) + return key, coreerr.E("MnemonicDecode", fmt.Sprintf("wallet: unknown mnemonic word %q", word), nil) } val := uint32(w1) + diff --git a/wallet/ring.go b/wallet/ring.go index 11493b1..adb6688 100644 --- a/wallet/ring.go +++ b/wallet/ring.go @@ -12,6 +12,8 @@ package wallet import ( "fmt" + coreerr "forge.lthn.ai/core/go-log" + "forge.lthn.ai/core/go-blockchain/rpc" "forge.lthn.ai/core/go-blockchain/types" ) @@ -42,7 +44,7 @@ func NewRPCRingSelector(client *rpc.Client) *RPCRingSelector { func (s *RPCRingSelector) SelectRing(amount uint64, realGlobalIndex uint64, ringSize int) ([]RingMember, error) { outs, err := s.client.GetRandomOutputs(amount, ringSize+5) if err != nil { - return nil, fmt.Errorf("wallet: get random outputs: %w", err) + return nil, coreerr.E("RPCRingSelector.SelectRing", "wallet: get random outputs", err) } var members []RingMember @@ -68,8 +70,7 @@ func (s *RPCRingSelector) SelectRing(amount uint64, realGlobalIndex uint64, ring } if len(members) < ringSize { - return nil, fmt.Errorf("wallet: insufficient decoys: got %d, need %d", - len(members), ringSize) + return nil, coreerr.E("RPCRingSelector.SelectRing", fmt.Sprintf("wallet: insufficient decoys: got %d, need %d", len(members), ringSize), nil) } return members, nil } diff --git a/wallet/signer.go b/wallet/signer.go index 842e684..83ed3a7 100644 --- a/wallet/signer.go +++ b/wallet/signer.go @@ -10,10 +10,9 @@ package wallet import ( - "fmt" - "forge.lthn.ai/core/go-blockchain/crypto" "forge.lthn.ai/core/go-blockchain/types" + coreerr "forge.lthn.ai/core/go-log" ) // Signer produces signatures for transaction inputs. @@ -35,7 +34,7 @@ func (s *NLSAGSigner) SignInput(prefixHash types.Hash, ephemeral KeyPair, ki, err := crypto.GenerateKeyImage( [32]byte(ephemeral.Public), [32]byte(ephemeral.Secret)) if err != nil { - return nil, fmt.Errorf("wallet: key image: %w", err) + return nil, coreerr.E("NLSAGSigner.SignInput", "wallet: key image", err) } pubs := make([][32]byte, len(ring)) @@ -47,7 +46,7 @@ func (s *NLSAGSigner) SignInput(prefixHash types.Hash, ephemeral KeyPair, [32]byte(prefixHash), ki, pubs, [32]byte(ephemeral.Secret), realIndex) if err != nil { - return nil, fmt.Errorf("wallet: ring signature: %w", err) + return nil, coreerr.E("NLSAGSigner.SignInput", "wallet: ring signature", err) } sigs := make([]types.Signature, len(rawSigs)) diff --git a/wallet/transfer.go b/wallet/transfer.go index 125c4da..e208bbb 100644 --- a/wallet/transfer.go +++ b/wallet/transfer.go @@ -13,6 +13,8 @@ import ( "encoding/json" "fmt" + coreerr "forge.lthn.ai/core/go-log" + store "forge.lthn.ai/core/go-store" "forge.lthn.ai/core/go-blockchain/config" @@ -66,7 +68,7 @@ func (t *Transfer) IsSpendable(chainHeight uint64, _ bool) bool { func putTransfer(s *store.Store, tr *Transfer) error { val, err := json.Marshal(tr) if err != nil { - return fmt.Errorf("wallet: marshal transfer: %w", err) + return coreerr.E("putTransfer", "wallet: marshal transfer", err) } return s.Set(groupTransfers, tr.KeyImage.String(), string(val)) } @@ -75,11 +77,11 @@ func putTransfer(s *store.Store, tr *Transfer) error { func getTransfer(s *store.Store, ki types.KeyImage) (*Transfer, error) { val, err := s.Get(groupTransfers, ki.String()) if err != nil { - return nil, fmt.Errorf("wallet: get transfer %s: %w", ki, err) + return nil, coreerr.E("getTransfer", fmt.Sprintf("wallet: get transfer %s", ki), err) } var tr Transfer if err := json.Unmarshal([]byte(val), &tr); err != nil { - return nil, fmt.Errorf("wallet: unmarshal transfer: %w", err) + return nil, coreerr.E("getTransfer", "wallet: unmarshal transfer", err) } return &tr, nil } @@ -100,7 +102,7 @@ func markTransferSpent(s *store.Store, ki types.KeyImage, height uint64) error { func listTransfers(s *store.Store) ([]Transfer, error) { pairs, err := s.GetAll(groupTransfers) if err != nil { - return nil, fmt.Errorf("wallet: list transfers: %w", err) + return nil, coreerr.E("listTransfers", "wallet: list transfers", err) } transfers := make([]Transfer, 0, len(pairs)) for _, val := range pairs { diff --git a/wallet/wallet.go b/wallet/wallet.go index 1df06e9..8fba7aa 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -11,11 +11,12 @@ package wallet import ( "cmp" - "errors" "fmt" "slices" "strconv" + coreerr "forge.lthn.ai/core/go-log" + "forge.lthn.ai/core/go-blockchain/chain" "forge.lthn.ai/core/go-blockchain/rpc" "forge.lthn.ai/core/go-blockchain/types" @@ -70,13 +71,13 @@ func (w *Wallet) Sync() error { chainHeight, err := w.chain.Height() if err != nil { - return fmt.Errorf("wallet: chain height: %w", err) + return coreerr.E("Wallet.Sync", "wallet: chain height", err) } for h := lastScanned; h < chainHeight; h++ { blk, _, err := w.chain.GetBlockByHeight(h) if err != nil { - return fmt.Errorf("wallet: get block %d: %w", h, err) + return coreerr.E("Wallet.Sync", fmt.Sprintf("wallet: get block %d", h), err) } // Scan miner tx. @@ -115,7 +116,7 @@ func (w *Wallet) scanTx(tx *types.Transaction, blockHeight uint64) error { } for i := range transfers { if err := putTransfer(w.store, &transfers[i]); err != nil { - return fmt.Errorf("wallet: store transfer: %w", err) + return coreerr.E("Wallet.scanTx", "wallet: store transfer", err) } } @@ -167,7 +168,7 @@ func (w *Wallet) Balance() (confirmed, locked uint64, err error) { // Send constructs and submits a transaction. func (w *Wallet) Send(destinations []Destination, fee uint64) (*types.Transaction, error) { if w.builder == nil || w.client == nil { - return nil, errors.New("wallet: no RPC client configured") + return nil, coreerr.E("Wallet.Send", "wallet: no RPC client configured", nil) } chainHeight, err := w.chain.Height() @@ -208,8 +209,7 @@ func (w *Wallet) Send(destinations []Destination, fee uint64) (*types.Transactio } } if selectedSum < needed { - return nil, fmt.Errorf("wallet: insufficient balance: have %d, need %d", - selectedSum, needed) + return nil, coreerr.E("Wallet.Send", fmt.Sprintf("wallet: insufficient balance: have %d, need %d", selectedSum, needed), nil) } req := &BuildRequest{ @@ -230,7 +230,7 @@ func (w *Wallet) Send(destinations []Destination, fee uint64) (*types.Transactio } if err := w.client.SendRawTransaction(blob); err != nil { - return nil, fmt.Errorf("wallet: submit tx: %w", err) + return nil, coreerr.E("Wallet.Send", "wallet: submit tx", err) } // Optimistically mark sources as spent. diff --git a/wallet/wordlist.go b/wallet/wordlist.go index 01b23f6..35253a6 100644 --- a/wallet/wordlist.go +++ b/wallet/wordlist.go @@ -1640,4 +1640,3 @@ func init() { wordIndex[w] = i } } - diff --git a/wire/decoder.go b/wire/decoder.go index 94b5987..06e9548 100644 --- a/wire/decoder.go +++ b/wire/decoder.go @@ -9,6 +9,8 @@ import ( "encoding/binary" "fmt" "io" + + coreerr "forge.lthn.ai/core/go-log" ) // Decoder reads consensus-critical binary data from an io.Reader. @@ -80,7 +82,7 @@ func (d *Decoder) ReadBytes(n int) []byte { return nil } if n < 0 || n > MaxBlobSize { - d.err = fmt.Errorf("wire: blob size %d exceeds maximum %d", n, MaxBlobSize) + d.err = coreerr.E("Decoder.ReadBytes", fmt.Sprintf("wire: blob size %d exceeds maximum %d", n, MaxBlobSize), nil) return nil } buf := make([]byte, n) diff --git a/wire/transaction.go b/wire/transaction.go index eb24f42..54c04fb 100644 --- a/wire/transaction.go +++ b/wire/transaction.go @@ -8,6 +8,8 @@ package wire import ( "fmt" + coreerr "forge.lthn.ai/core/go-log" + "forge.lthn.ai/core/go-blockchain/types" ) @@ -162,21 +164,6 @@ func encodeInputs(enc *Encoder, vin []types.TxInput) { encodeKeyOffsets(enc, v.KeyOffsets) enc.WriteBlob32((*[32]byte)(&v.KeyImage)) enc.WriteBytes(v.EtcDetails) - case types.TxInputHTLC: - // Wire order: hltc_origin (string) BEFORE parent fields (C++ quirk). - enc.WriteVarint(uint64(len(v.HTLCOrigin))) - if len(v.HTLCOrigin) > 0 { - enc.WriteBytes([]byte(v.HTLCOrigin)) - } - enc.WriteVarint(v.Amount) - encodeKeyOffsets(enc, v.KeyOffsets) - enc.WriteBlob32((*[32]byte)(&v.KeyImage)) - enc.WriteBytes(v.EtcDetails) - case types.TxInputMultisig: - enc.WriteVarint(v.Amount) - enc.WriteBlob32((*[32]byte)(&v.MultisigOutID)) - enc.WriteVarint(v.SigsCount) - enc.WriteBytes(v.EtcDetails) } } } @@ -208,27 +195,8 @@ func decodeInputs(dec *Decoder) []types.TxInput { dec.ReadBlob32((*[32]byte)(&in.KeyImage)) in.EtcDetails = decodeRawVariantVector(dec) vin = append(vin, in) - case types.InputTypeHTLC: - var in types.TxInputHTLC - // Wire order: hltc_origin (string) BEFORE parent fields. - originLen := dec.ReadVarint() - if originLen > 0 && dec.Err() == nil { - in.HTLCOrigin = string(dec.ReadBytes(int(originLen))) - } - in.Amount = dec.ReadVarint() - in.KeyOffsets = decodeKeyOffsets(dec) - dec.ReadBlob32((*[32]byte)(&in.KeyImage)) - in.EtcDetails = decodeRawVariantVector(dec) - vin = append(vin, in) - case types.InputTypeMultisig: - var in types.TxInputMultisig - in.Amount = dec.ReadVarint() - dec.ReadBlob32((*[32]byte)(&in.MultisigOutID)) - in.SigsCount = dec.ReadVarint() - in.EtcDetails = decodeRawVariantVector(dec) - vin = append(vin, in) default: - dec.err = fmt.Errorf("wire: unsupported input tag 0x%02x", tag) + dec.err = coreerr.E("decodeInputs", fmt.Sprintf("wire: unsupported input tag 0x%02x", tag), nil) return vin } } @@ -266,7 +234,7 @@ func decodeKeyOffsets(dec *Decoder) []types.TxOutRef { dec.ReadBlob32((*[32]byte)(&refs[i].TxID)) refs[i].N = dec.ReadVarint() default: - dec.err = fmt.Errorf("wire: unsupported ref tag 0x%02x", refs[i].Tag) + dec.err = coreerr.E("decodeKeyOffsets", fmt.Sprintf("wire: unsupported ref tag 0x%02x", refs[i].Tag), nil) return refs } } @@ -284,26 +252,9 @@ func encodeOutputsV1(enc *Encoder, vout []types.TxOutput) { case types.TxOutputBare: enc.WriteVarint(v.Amount) // Target is a variant (txout_target_v) - switch tgt := v.Target.(type) { - case types.TxOutToKey: - enc.WriteVariantTag(types.TargetTypeToKey) - enc.WriteBlob32((*[32]byte)(&tgt.Key)) - enc.WriteUint8(tgt.MixAttr) - case types.TxOutMultisig: - enc.WriteVariantTag(types.TargetTypeMultisig) - enc.WriteVarint(tgt.MinimumSigs) - enc.WriteVarint(uint64(len(tgt.Keys))) - for i := range tgt.Keys { - enc.WriteBlob32((*[32]byte)(&tgt.Keys[i])) - } - case types.TxOutHTLC: - enc.WriteVariantTag(types.TargetTypeHTLC) - enc.WriteBlob32((*[32]byte)(&tgt.HTLCHash)) - enc.WriteUint8(tgt.Flags) - enc.WriteVarint(tgt.Expiration) - enc.WriteBlob32((*[32]byte)(&tgt.PKRedeem)) - enc.WriteBlob32((*[32]byte)(&tgt.PKRefund)) - } + enc.WriteVariantTag(types.TargetTypeToKey) + enc.WriteBlob32((*[32]byte)(&v.Target.Key)) + enc.WriteUint8(v.Target.MixAttr) } } } @@ -323,31 +274,10 @@ func decodeOutputsV1(dec *Decoder) []types.TxOutput { } switch tag { case types.TargetTypeToKey: - var tgt types.TxOutToKey - dec.ReadBlob32((*[32]byte)(&tgt.Key)) - tgt.MixAttr = dec.ReadUint8() - out.Target = tgt - case types.TargetTypeMultisig: - var tgt types.TxOutMultisig - tgt.MinimumSigs = dec.ReadVarint() - keyCount := dec.ReadVarint() - if keyCount > 0 && dec.Err() == nil { - tgt.Keys = make([]types.PublicKey, keyCount) - for j := uint64(0); j < keyCount; j++ { - dec.ReadBlob32((*[32]byte)(&tgt.Keys[j])) - } - } - out.Target = tgt - case types.TargetTypeHTLC: - var tgt types.TxOutHTLC - dec.ReadBlob32((*[32]byte)(&tgt.HTLCHash)) - tgt.Flags = dec.ReadUint8() - tgt.Expiration = dec.ReadVarint() - dec.ReadBlob32((*[32]byte)(&tgt.PKRedeem)) - dec.ReadBlob32((*[32]byte)(&tgt.PKRefund)) - out.Target = tgt + dec.ReadBlob32((*[32]byte)(&out.Target.Key)) + out.Target.MixAttr = dec.ReadUint8() default: - dec.err = fmt.Errorf("wire: unsupported target tag 0x%02x", tag) + dec.err = coreerr.E("decodeOutputsV1", fmt.Sprintf("wire: unsupported target tag 0x%02x", tag), nil) return vout } vout = append(vout, out) @@ -363,26 +293,9 @@ func encodeOutputsV2(enc *Encoder, vout []types.TxOutput) { switch v := out.(type) { case types.TxOutputBare: enc.WriteVarint(v.Amount) - switch tgt := v.Target.(type) { - case types.TxOutToKey: - enc.WriteVariantTag(types.TargetTypeToKey) - enc.WriteBlob32((*[32]byte)(&tgt.Key)) - enc.WriteUint8(tgt.MixAttr) - case types.TxOutMultisig: - enc.WriteVariantTag(types.TargetTypeMultisig) - enc.WriteVarint(tgt.MinimumSigs) - enc.WriteVarint(uint64(len(tgt.Keys))) - for i := range tgt.Keys { - enc.WriteBlob32((*[32]byte)(&tgt.Keys[i])) - } - case types.TxOutHTLC: - enc.WriteVariantTag(types.TargetTypeHTLC) - enc.WriteBlob32((*[32]byte)(&tgt.HTLCHash)) - enc.WriteUint8(tgt.Flags) - enc.WriteVarint(tgt.Expiration) - enc.WriteBlob32((*[32]byte)(&tgt.PKRedeem)) - enc.WriteBlob32((*[32]byte)(&tgt.PKRefund)) - } + enc.WriteVariantTag(types.TargetTypeToKey) + enc.WriteBlob32((*[32]byte)(&v.Target.Key)) + enc.WriteUint8(v.Target.MixAttr) case types.TxOutputZarcanum: enc.WriteBlob32((*[32]byte)(&v.StealthAddress)) enc.WriteBlob32((*[32]byte)(&v.ConcealingPoint)) @@ -410,36 +323,11 @@ func decodeOutputsV2(dec *Decoder) []types.TxOutput { var out types.TxOutputBare out.Amount = dec.ReadVarint() targetTag := dec.ReadVariantTag() - if dec.Err() != nil { - return vout - } - switch targetTag { - case types.TargetTypeToKey: - var tgt types.TxOutToKey - dec.ReadBlob32((*[32]byte)(&tgt.Key)) - tgt.MixAttr = dec.ReadUint8() - out.Target = tgt - case types.TargetTypeMultisig: - var tgt types.TxOutMultisig - tgt.MinimumSigs = dec.ReadVarint() - keyCount := dec.ReadVarint() - if keyCount > 0 && dec.Err() == nil { - tgt.Keys = make([]types.PublicKey, keyCount) - for j := uint64(0); j < keyCount; j++ { - dec.ReadBlob32((*[32]byte)(&tgt.Keys[j])) - } - } - out.Target = tgt - case types.TargetTypeHTLC: - var tgt types.TxOutHTLC - dec.ReadBlob32((*[32]byte)(&tgt.HTLCHash)) - tgt.Flags = dec.ReadUint8() - tgt.Expiration = dec.ReadVarint() - dec.ReadBlob32((*[32]byte)(&tgt.PKRedeem)) - dec.ReadBlob32((*[32]byte)(&tgt.PKRefund)) - out.Target = tgt - default: - dec.err = fmt.Errorf("wire: unsupported target tag 0x%02x", targetTag) + if targetTag == types.TargetTypeToKey { + dec.ReadBlob32((*[32]byte)(&out.Target.Key)) + out.Target.MixAttr = dec.ReadUint8() + } else { + dec.err = coreerr.E("decodeOutputsV2", fmt.Sprintf("wire: unsupported target tag 0x%02x", targetTag), nil) return vout } vout = append(vout, out) @@ -453,7 +341,7 @@ func decodeOutputsV2(dec *Decoder) []types.TxOutput { out.MixAttr = dec.ReadUint8() vout = append(vout, out) default: - dec.err = fmt.Errorf("wire: unsupported output tag 0x%02x", tag) + dec.err = coreerr.E("decodeOutputsV2", fmt.Sprintf("wire: unsupported output tag 0x%02x", tag), nil) return vout } } @@ -509,40 +397,32 @@ func decodeRawVariantVector(dec *Decoder) []byte { // These are used by readVariantElementData to determine element boundaries // when reading raw variant vectors (extra, attachment, etc_details). const ( - tagTxComment = 7 // tx_comment — string - tagTxPayerOld = 8 // tx_payer_old — 2 public keys - tagString = 9 // std::string — string - tagTxCryptoChecksum = 10 // tx_crypto_checksum — two uint32 LE - tagTxDerivationHint = 11 // tx_derivation_hint — string - tagTxServiceAttachment = 12 // tx_service_attachment — 3 strings + vector + uint8 - tagUnlockTime = 14 // etc_tx_details_unlock_time — varint - tagExpirationTime = 15 // etc_tx_details_expiration_time — varint - tagTxDetailsFlags = 16 // etc_tx_details_flags — varint - tagSignedParts = 17 // signed_parts — two varints (n_outs, n_extras) - tagExtraAttachmentInfo = 18 // extra_attachment_info — string + hash + varint - tagExtraUserData = 19 // extra_user_data — string - tagExtraAliasEntryOld = 20 // extra_alias_entry_old — complex - tagExtraPadding = 21 // extra_padding — vector - tagPublicKey = 22 // crypto::public_key — 32 bytes - tagEtcTxFlags16 = 23 // etc_tx_flags16_t — uint16 LE - tagUint16 = 24 // uint16_t — uint16 LE - tagUint64 = 26 // uint64_t — varint - tagEtcTxTime = 27 // etc_tx_time — varint - tagUint32 = 28 // uint32_t — uint32 LE - tagTxReceiverOld = 29 // tx_receiver_old — 2 public keys - tagUnlockTime2 = 30 // etc_tx_details_unlock_time2 — vector of entries - tagTxPayer = 31 // tx_payer — 2 keys + optional flag - tagTxReceiver = 32 // tx_receiver — 2 keys + optional flag - tagExtraAliasEntry = 33 // extra_alias_entry — complex - tagZarcanumTxDataV1 = 39 // zarcanum_tx_data_v1 — varint (fee) - - // Asset descriptor operation (HF5). - tagAssetDescriptorOperation = 40 // asset_descriptor_operation - - // Asset operation proof tags (HF5). - tagAssetOperationProof = 49 // asset_operation_proof - tagAssetOperationOwnershipProof = 50 // asset_operation_ownership_proof - tagAssetOperationOwnershipProofETH = 51 // asset_operation_ownership_proof_eth (Ethereum sig) + tagTxComment = 7 // tx_comment — string + tagTxPayerOld = 8 // tx_payer_old — 2 public keys + tagString = 9 // std::string — string + tagTxCryptoChecksum = 10 // tx_crypto_checksum — two uint32 LE + tagTxDerivationHint = 11 // tx_derivation_hint — string + tagTxServiceAttachment = 12 // tx_service_attachment — 3 strings + vector + uint8 + tagUnlockTime = 14 // etc_tx_details_unlock_time — varint + tagExpirationTime = 15 // etc_tx_details_expiration_time — varint + tagTxDetailsFlags = 16 // etc_tx_details_flags — varint + tagSignedParts = 17 // signed_parts — two varints (n_outs, n_extras) + tagExtraAttachmentInfo = 18 // extra_attachment_info — string + hash + varint + tagExtraUserData = 19 // extra_user_data — string + tagExtraAliasEntryOld = 20 // extra_alias_entry_old — complex + tagExtraPadding = 21 // extra_padding — vector + tagPublicKey = 22 // crypto::public_key — 32 bytes + tagEtcTxFlags16 = 23 // etc_tx_flags16_t — uint16 LE + tagUint16 = 24 // uint16_t — uint16 LE + tagUint64 = 26 // uint64_t — varint + tagEtcTxTime = 27 // etc_tx_time — varint + tagUint32 = 28 // uint32_t — uint32 LE + tagTxReceiverOld = 29 // tx_receiver_old — 2 public keys + tagUnlockTime2 = 30 // etc_tx_details_unlock_time2 — vector of entries + tagTxPayer = 31 // tx_payer — 2 keys + optional flag + tagTxReceiver = 32 // tx_receiver — 2 keys + optional flag + tagExtraAliasEntry = 33 // extra_alias_entry — complex + tagZarcanumTxDataV1 = 39 // zarcanum_tx_data_v1 — varint (fee) // Signature variant tags (signature_v). tagNLSAGSig = 42 // NLSAG_sig — vector @@ -612,18 +492,6 @@ func readVariantElementData(dec *Decoder, tag uint8) []byte { case tagZarcanumTxDataV1: // fee — FIELD(fee) → serialize_int → 8-byte LE return dec.ReadBytes(8) - // Asset descriptor operation (HF5) - case tagAssetDescriptorOperation: - return readAssetDescriptorOperation(dec) - - // Asset operation proof variants (HF5) - case tagAssetOperationProof: - return readAssetOperationProof(dec) - case tagAssetOperationOwnershipProof: - return readAssetOperationOwnershipProof(dec) - case tagAssetOperationOwnershipProofETH: - return readAssetOperationOwnershipProofETH(dec) - // Signature variants case tagNLSAGSig: // vector (64 bytes each) return readVariantVectorFixed(dec, 64) @@ -643,7 +511,7 @@ func readVariantElementData(dec *Decoder, tag uint8) []byte { return dec.ReadBytes(96) default: - dec.err = fmt.Errorf("wire: unsupported variant tag 0x%02x (%d)", tag, tag) + dec.err = coreerr.E("readVariantElementData", fmt.Sprintf("wire: unsupported variant tag 0x%02x (%d)", tag, tag), nil) return nil } } @@ -804,261 +672,6 @@ func readSignedParts(dec *Decoder) []byte { return raw } -// --- asset operation readers (HF5) --- - -// readAssetDescriptorOperation reads asset_descriptor_operation (tag 40). -// Structure (CHAIN_TRANSITION_VER, version 0 and 1): -// -// ver (uint8) + operation_type (uint8) -// + opt_asset_id (uint8 marker + 32 bytes if present) -// + opt_descriptor (uint8 marker + AssetDescriptorBase if present) -// + amount_to_emit (uint64 LE) + amount_to_burn (uint64 LE) -// + etc (vector) -// -// AssetDescriptorBase: -// -// ticker (string) + full_name (string) + total_max_supply (uint64 LE) -// + current_supply (uint64 LE) + decimal_point (uint8) + meta_info (string) -// + owner_key (32 bytes) + etc (vector) -func readAssetDescriptorOperation(dec *Decoder) []byte { - var raw []byte - - // ver: uint8 (CHAIN_TRANSITION_VER version byte) - ver := dec.ReadUint8() - if dec.err != nil { - return nil - } - raw = append(raw, ver) - - // operation_type: uint8 - opType := dec.ReadUint8() - if dec.err != nil { - return nil - } - raw = append(raw, opType) - - // opt_asset_id: optional — uint8 marker, then 32 bytes if present - marker := dec.ReadUint8() - if dec.err != nil { - return nil - } - raw = append(raw, marker) - if marker != 0 { - b := dec.ReadBytes(32) - if dec.err != nil { - return nil - } - raw = append(raw, b...) - } - - // opt_descriptor: optional - marker = dec.ReadUint8() - if dec.err != nil { - return nil - } - raw = append(raw, marker) - if marker != 0 { - b := readAssetDescriptorBase(dec) - if dec.err != nil { - return nil - } - raw = append(raw, b...) - } - - // amount_to_emit: uint64 LE - b := dec.ReadBytes(8) - if dec.err != nil { - return nil - } - raw = append(raw, b...) - - // amount_to_burn: uint64 LE - b = dec.ReadBytes(8) - if dec.err != nil { - return nil - } - raw = append(raw, b...) - - // etc: vector - v := readVariantVectorFixed(dec, 1) - if dec.err != nil { - return nil - } - raw = append(raw, v...) - - return raw -} - -// readAssetDescriptorBase reads the AssetDescriptorBase structure. -// Wire: ticker (string) + full_name (string) + total_max_supply (uint64 LE) -// -// + current_supply (uint64 LE) + decimal_point (uint8) + meta_info (string) -// + owner_key (32 bytes) + etc (vector). -func readAssetDescriptorBase(dec *Decoder) []byte { - var raw []byte - - // ticker: string - s := readStringBlob(dec) - if dec.err != nil { - return nil - } - raw = append(raw, s...) - - // full_name: string - s = readStringBlob(dec) - if dec.err != nil { - return nil - } - raw = append(raw, s...) - - // total_max_supply: uint64 LE - b := dec.ReadBytes(8) - if dec.err != nil { - return nil - } - raw = append(raw, b...) - - // current_supply: uint64 LE - b = dec.ReadBytes(8) - if dec.err != nil { - return nil - } - raw = append(raw, b...) - - // decimal_point: uint8 - dp := dec.ReadUint8() - if dec.err != nil { - return nil - } - raw = append(raw, dp) - - // meta_info: string - s = readStringBlob(dec) - if dec.err != nil { - return nil - } - raw = append(raw, s...) - - // owner_key: 32 bytes (crypto::public_key) - b = dec.ReadBytes(32) - if dec.err != nil { - return nil - } - raw = append(raw, b...) - - // etc: vector - v := readVariantVectorFixed(dec, 1) - if dec.err != nil { - return nil - } - raw = append(raw, v...) - - return raw -} - -// readAssetOperationProof reads asset_operation_proof (tag 49). -// Structure (CHAIN_TRANSITION_VER, version 1): -// -// ver (uint8) + gss (generic_schnorr_sig_s: 64 bytes) -// + asset_id (32 bytes) + etc (vector). -func readAssetOperationProof(dec *Decoder) []byte { - var raw []byte - - // ver: uint8 - ver := dec.ReadUint8() - if dec.err != nil { - return nil - } - raw = append(raw, ver) - - // gss: generic_schnorr_sig_s — 2 scalars (s, c) = 64 bytes - b := dec.ReadBytes(64) - if dec.err != nil { - return nil - } - raw = append(raw, b...) - - // asset_id: 32-byte hash - b = dec.ReadBytes(32) - if dec.err != nil { - return nil - } - raw = append(raw, b...) - - // etc: vector - v := readVariantVectorFixed(dec, 1) - if dec.err != nil { - return nil - } - raw = append(raw, v...) - - return raw -} - -// readAssetOperationOwnershipProof reads asset_operation_ownership_proof (tag 50). -// Structure (CHAIN_TRANSITION_VER, version 1): -// -// ver (uint8) + gss (generic_schnorr_sig_s: 64 bytes) -// + etc (vector). -func readAssetOperationOwnershipProof(dec *Decoder) []byte { - var raw []byte - - // ver: uint8 - ver := dec.ReadUint8() - if dec.err != nil { - return nil - } - raw = append(raw, ver) - - // gss: generic_schnorr_sig_s — 2 scalars (s, c) = 64 bytes - b := dec.ReadBytes(64) - if dec.err != nil { - return nil - } - raw = append(raw, b...) - - // etc: vector - v := readVariantVectorFixed(dec, 1) - if dec.err != nil { - return nil - } - raw = append(raw, v...) - - return raw -} - -// readAssetOperationOwnershipProofETH reads asset_operation_ownership_proof_eth (tag 51). -// Structure (CHAIN_TRANSITION_VER, version 1): -// -// ver (uint8) + eth_sig (65 bytes: r(32) + s(32) + v(1)) -// + etc (vector). -func readAssetOperationOwnershipProofETH(dec *Decoder) []byte { - var raw []byte - - // ver: uint8 - ver := dec.ReadUint8() - if dec.err != nil { - return nil - } - raw = append(raw, ver) - - // eth_sig: crypto::eth_signature — r(32) + s(32) + v(1) = 65 bytes - b := dec.ReadBytes(65) - if dec.err != nil { - return nil - } - raw = append(raw, b...) - - // etc: vector - v := readVariantVectorFixed(dec, 1) - if dec.err != nil { - return nil - } - raw = append(raw, v...) - - return raw -} - // --- crypto blob readers --- // These read variable-length serialised crypto structures and return raw bytes. // All vectors are varint(count) + 32*count bytes (scalars or points). @@ -1247,7 +860,7 @@ func readZCSig(dec *Decoder) []byte { // readZarcanumSig reads zarcanum_sig (tag 45). // Wire: d(32) + C(32) + C'(32) + E(32) + c(32) + y0(32) + y1(32) + y2(32) + y3(32) + y4(32) // -// + bppe_serialized + pseudo_out_amount_commitment(32) + CLSAG_GGXXG. +// - bppe_serialized + pseudo_out_amount_commitment(32) + CLSAG_GGXXG. func readZarcanumSig(dec *Decoder) []byte { var raw []byte // 10 fixed scalars/points