diff --git a/chain/index.go b/chain/index.go index b35495d..27787a5 100644 --- a/chain/index.go +++ b/chain/index.go @@ -6,11 +6,9 @@ package chain import ( - "encoding/json" - "errors" - "fmt" "strconv" + "dappco.re/go/core" coreerr "dappco.re/go/core/log" "dappco.re/go/core/blockchain/types" @@ -20,7 +18,7 @@ import ( // 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 coreerr.E("Chain.MarkSpent", fmt.Sprintf("chain: mark spent %s", ki), err) + return coreerr.E("Chain.MarkSpent", core.Sprintf("chain: mark spent %s", ki), err) } return nil } @@ -28,11 +26,11 @@ func (c *Chain) MarkSpent(ki types.KeyImage, height uint64) error { // IsSpent checks whether a key image has been spent. func (c *Chain) IsSpent(ki types.KeyImage) (bool, error) { _, err := c.store.Get(groupSpentKeys, ki.String()) - if errors.Is(err, store.ErrNotFound) { + if core.Is(err, store.ErrNotFound) { return false, nil } if err != nil { - return false, coreerr.E("Chain.IsSpent", fmt.Sprintf("chain: check spent %s", ki), err) + return false, coreerr.E("Chain.IsSpent", core.Sprintf("chain: check spent %s", ki), err) } return true, nil } @@ -56,13 +54,10 @@ func (c *Chain) PutOutput(amount uint64, txID types.Hash, outNo uint32) (uint64, TxID: txID.String(), OutNo: outNo, } - val, err := json.Marshal(entry) - if err != nil { - return 0, coreerr.E("Chain.PutOutput", "chain: marshal output", err) - } + val := core.JSONMarshalString(entry) key := strconv.FormatUint(gindex, 10) - if err := c.store.Set(grp, key, string(val)); err != nil { + if err := c.store.Set(grp, key, val); err != nil { return 0, coreerr.E("Chain.PutOutput", "chain: store output", err) } return gindex, nil @@ -74,15 +69,15 @@ func (c *Chain) GetOutput(amount uint64, gindex uint64) (types.Hash, uint32, err key := strconv.FormatUint(gindex, 10) val, err := c.store.Get(grp, key) if err != nil { - if errors.Is(err, store.ErrNotFound) { - return types.Hash{}, 0, coreerr.E("Chain.GetOutput", fmt.Sprintf("chain: output %d:%d not found", amount, gindex), nil) + if core.Is(err, store.ErrNotFound) { + return types.Hash{}, 0, coreerr.E("Chain.GetOutput", core.Sprintf("chain: output %d:%d not found", amount, gindex), nil) } 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, coreerr.E("Chain.GetOutput", "chain: unmarshal output", err) + if r := core.JSONUnmarshalString(val, &entry); !r.OK { + return types.Hash{}, 0, coreerr.E("Chain.GetOutput", "chain: unmarshal output", r.Value.(error)) } hash, err := types.HashFromHex(entry.TxID) if err != nil { diff --git a/chain/p2psync.go b/chain/p2psync.go index 5bdfbfe..6f40ee9 100644 --- a/chain/p2psync.go +++ b/chain/p2psync.go @@ -7,9 +7,9 @@ package chain import ( "context" - "fmt" "log" + "dappco.re/go/core" coreerr "dappco.re/go/core/log" ) @@ -120,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 coreerr.E("Chain.P2PSync", fmt.Sprintf("p2p sync: compute difficulty for block %d", blockHeight), err) + return coreerr.E("Chain.P2PSync", core.Sprintf("p2p sync: compute difficulty for block %d", blockHeight), err) } if err := c.processBlockBlobs(entry.Block, entry.Txs, blockHeight, blockDiff, opts); err != nil { - return coreerr.E("Chain.P2PSync", fmt.Sprintf("p2p sync: process block %d", blockHeight), err) + return coreerr.E("Chain.P2PSync", core.Sprintf("p2p sync: process block %d", blockHeight), err) } } } diff --git a/chain/ring.go b/chain/ring.go index ba52d31..b17daeb 100644 --- a/chain/ring.go +++ b/chain/ring.go @@ -6,8 +6,7 @@ package chain import ( - "fmt" - + "dappco.re/go/core" coreerr "dappco.re/go/core/log" "dappco.re/go/core/blockchain/consensus" @@ -22,27 +21,27 @@ 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, coreerr.E("Chain.GetRingOutputs", fmt.Sprintf("ring output %d (amount=%d, gidx=%d)", i, amount, gidx), err) + return nil, coreerr.E("Chain.GetRingOutputs", core.Sprintf("ring output %d (amount=%d, gidx=%d)", i, amount, gidx), err) } tx, _, err := c.GetTransaction(txHash) if err != nil { - return nil, coreerr.E("Chain.GetRingOutputs", fmt.Sprintf("ring output %d: tx %s", i, txHash), err) + return nil, coreerr.E("Chain.GetRingOutputs", core.Sprintf("ring output %d: tx %s", i, txHash), err) } if int(outNo) >= len(tx.Vout) { - 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) + return nil, coreerr.E("Chain.GetRingOutputs", core.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) { case types.TxOutputBare: toKey, ok := out.Target.(types.TxOutToKey) if !ok { - return nil, coreerr.E("Chain.GetRingOutputs", fmt.Sprintf("ring output %d: unsupported target type %T", i, out.Target), nil) + return nil, coreerr.E("Chain.GetRingOutputs", core.Sprintf("ring output %d: unsupported target type %T", i, out.Target), nil) } pubs[i] = toKey.Key default: - return nil, coreerr.E("Chain.GetRingOutputs", fmt.Sprintf("ring output %d: unsupported output type %T", i, out), nil) + return nil, coreerr.E("Chain.GetRingOutputs", core.Sprintf("ring output %d: unsupported output type %T", i, out), nil) } } return pubs, nil @@ -58,16 +57,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, coreerr.E("Chain.GetZCRingOutputs", fmt.Sprintf("ZC ring output %d (gidx=%d)", i, gidx), err) + return nil, coreerr.E("Chain.GetZCRingOutputs", core.Sprintf("ZC ring output %d (gidx=%d)", i, gidx), err) } tx, _, err := c.GetTransaction(txHash) if err != nil { - return nil, coreerr.E("Chain.GetZCRingOutputs", fmt.Sprintf("ZC ring output %d: tx %s", i, txHash), err) + return nil, coreerr.E("Chain.GetZCRingOutputs", core.Sprintf("ZC ring output %d: tx %s", i, txHash), err) } if int(outNo) >= len(tx.Vout) { - 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) + return nil, coreerr.E("Chain.GetZCRingOutputs", core.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 +77,7 @@ func (c *Chain) GetZCRingOutputs(offsets []uint64) ([]consensus.ZCRingMember, er BlindedAssetID: [32]byte(out.BlindedAssetID), } default: - return nil, coreerr.E("Chain.GetZCRingOutputs", fmt.Sprintf("ZC ring output %d: expected TxOutputZarcanum, got %T", i, out), nil) + return nil, coreerr.E("Chain.GetZCRingOutputs", core.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 1f1fcb8..78eecab 100644 --- a/chain/store.go +++ b/chain/store.go @@ -8,11 +8,9 @@ package chain import ( "bytes" "encoding/hex" - "encoding/json" - "errors" - "fmt" "strconv" + "dappco.re/go/core" coreerr "dappco.re/go/core/log" "dappco.re/go/core/blockchain/types" @@ -31,7 +29,7 @@ const ( // heightKey returns a zero-padded 10-digit decimal key for the given height. func heightKey(h uint64) string { - return fmt.Sprintf("%010d", h) + return core.Sprintf("%010d", h) } // blockRecord is the JSON value stored in the blocks group. @@ -46,26 +44,23 @@ 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 coreerr.E("Chain.PutBlock", fmt.Sprintf("chain: encode block %d", meta.Height), err) + return coreerr.E("Chain.PutBlock", core.Sprintf("chain: encode block %d", meta.Height), err) } rec := blockRecord{ Meta: *meta, Blob: hex.EncodeToString(buf.Bytes()), } - val, err := json.Marshal(rec) - if err != nil { - return coreerr.E("Chain.PutBlock", fmt.Sprintf("chain: marshal block %d", meta.Height), err) - } + val := core.JSONMarshalString(rec) - if err := c.store.Set(groupBlocks, heightKey(meta.Height), string(val)); err != nil { - return coreerr.E("Chain.PutBlock", fmt.Sprintf("chain: store block %d", meta.Height), err) + if err := c.store.Set(groupBlocks, heightKey(meta.Height), val); err != nil { + return coreerr.E("Chain.PutBlock", core.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 coreerr.E("Chain.PutBlock", fmt.Sprintf("chain: index block %d", meta.Height), err) + return coreerr.E("Chain.PutBlock", core.Sprintf("chain: index block %d", meta.Height), err) } return nil @@ -75,10 +70,10 @@ func (c *Chain) PutBlock(b *types.Block, meta *BlockMeta) error { 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, coreerr.E("Chain.GetBlockByHeight", fmt.Sprintf("chain: block %d not found", height), nil) + if core.Is(err, store.ErrNotFound) { + return nil, nil, coreerr.E("Chain.GetBlockByHeight", core.Sprintf("chain: block %d not found", height), nil) } - return nil, nil, coreerr.E("Chain.GetBlockByHeight", fmt.Sprintf("chain: get block %d", height), err) + return nil, nil, coreerr.E("Chain.GetBlockByHeight", core.Sprintf("chain: get block %d", height), err) } return decodeBlockRecord(val) } @@ -87,14 +82,14 @@ func (c *Chain) GetBlockByHeight(height uint64) (*types.Block, *BlockMeta, error 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, coreerr.E("Chain.GetBlockByHash", fmt.Sprintf("chain: block %s not found", hash), nil) + if core.Is(err, store.ErrNotFound) { + return nil, nil, coreerr.E("Chain.GetBlockByHash", core.Sprintf("chain: block %s not found", hash), nil) } - return nil, nil, coreerr.E("Chain.GetBlockByHash", fmt.Sprintf("chain: get block index %s", hash), err) + return nil, nil, coreerr.E("Chain.GetBlockByHash", core.Sprintf("chain: get block index %s", hash), err) } height, err := strconv.ParseUint(heightStr, 10, 64) if err != nil { - return nil, nil, coreerr.E("Chain.GetBlockByHash", fmt.Sprintf("chain: parse height %q", heightStr), err) + return nil, nil, coreerr.E("Chain.GetBlockByHash", core.Sprintf("chain: parse height %q", heightStr), err) } return c.GetBlockByHeight(height) } @@ -111,20 +106,17 @@ 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 coreerr.E("Chain.PutTransaction", fmt.Sprintf("chain: encode tx %s", hash), err) + return coreerr.E("Chain.PutTransaction", core.Sprintf("chain: encode tx %s", hash), err) } rec := txRecord{ Meta: *meta, Blob: hex.EncodeToString(buf.Bytes()), } - val, err := json.Marshal(rec) - if err != nil { - return coreerr.E("Chain.PutTransaction", fmt.Sprintf("chain: marshal tx %s", hash), err) - } + val := core.JSONMarshalString(rec) - if err := c.store.Set(groupTx, hash.String(), string(val)); err != nil { - return coreerr.E("Chain.PutTransaction", fmt.Sprintf("chain: store tx %s", hash), err) + if err := c.store.Set(groupTx, hash.String(), val); err != nil { + return coreerr.E("Chain.PutTransaction", core.Sprintf("chain: store tx %s", hash), err) } return nil } @@ -133,15 +125,15 @@ func (c *Chain) PutTransaction(hash types.Hash, tx *types.Transaction, meta *TxM func (c *Chain) GetTransaction(hash types.Hash) (*types.Transaction, *TxMeta, error) { val, err := c.store.Get(groupTx, hash.String()) if err != nil { - if errors.Is(err, store.ErrNotFound) { - return nil, nil, coreerr.E("Chain.GetTransaction", fmt.Sprintf("chain: tx %s not found", hash), nil) + if core.Is(err, store.ErrNotFound) { + return nil, nil, coreerr.E("Chain.GetTransaction", core.Sprintf("chain: tx %s not found", hash), nil) } - return nil, nil, coreerr.E("Chain.GetTransaction", fmt.Sprintf("chain: get tx %s", hash), err) + return nil, nil, coreerr.E("Chain.GetTransaction", core.Sprintf("chain: get tx %s", hash), err) } var rec txRecord - if err := json.Unmarshal([]byte(val), &rec); err != nil { - return nil, nil, coreerr.E("Chain.GetTransaction", "chain: unmarshal tx", err) + if r := core.JSONUnmarshalString(val, &rec); !r.OK { + return nil, nil, coreerr.E("Chain.GetTransaction", "chain: unmarshal tx", r.Value.(error)) } blob, err := hex.DecodeString(rec.Blob) if err != nil { @@ -166,19 +158,19 @@ 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, coreerr.E("Chain.getBlockMeta", fmt.Sprintf("chain: block meta %d", height), err) + return nil, coreerr.E("Chain.getBlockMeta", core.Sprintf("chain: block meta %d", height), err) } var rec blockRecord - if err := json.Unmarshal([]byte(val), &rec); err != nil { - return nil, coreerr.E("Chain.getBlockMeta", fmt.Sprintf("chain: unmarshal block meta %d", height), err) + if r := core.JSONUnmarshalString(val, &rec); !r.OK { + return nil, coreerr.E("Chain.getBlockMeta", core.Sprintf("chain: unmarshal block meta %d", height), r.Value.(error)) } return &rec.Meta, nil } func decodeBlockRecord(val string) (*types.Block, *BlockMeta, error) { var rec blockRecord - if err := json.Unmarshal([]byte(val), &rec); err != nil { - return nil, nil, coreerr.E("decodeBlockRecord", "chain: unmarshal block", err) + if r := core.JSONUnmarshalString(val, &rec); !r.OK { + return nil, nil, coreerr.E("decodeBlockRecord", "chain: unmarshal block", r.Value.(error)) } blob, err := hex.DecodeString(rec.Blob) if err != nil { diff --git a/chain/sync.go b/chain/sync.go index 27d6154..c9705d0 100644 --- a/chain/sync.go +++ b/chain/sync.go @@ -9,12 +9,11 @@ import ( "bytes" "context" "encoding/hex" - "encoding/json" - "fmt" "log" "regexp" "strconv" + "dappco.re/go/core" coreerr "dappco.re/go/core/log" "dappco.re/go/core/blockchain/config" @@ -72,16 +71,16 @@ func (c *Chain) Sync(ctx context.Context, client *rpc.Client, opts SyncOptions) blocks, err := client.GetBlocksDetails(localHeight, batch) if err != nil { - return coreerr.E("Chain.Sync", fmt.Sprintf("sync: fetch blocks at %d", localHeight), err) + return coreerr.E("Chain.Sync", core.Sprintf("sync: fetch blocks at %d", localHeight), err) } if err := resolveBlockBlobs(blocks, client); err != nil { - return coreerr.E("Chain.Sync", fmt.Sprintf("sync: resolve blobs at %d", localHeight), err) + return coreerr.E("Chain.Sync", core.Sprintf("sync: resolve blobs at %d", localHeight), err) } for _, bd := range blocks { if err := c.processBlock(bd, opts); err != nil { - return coreerr.E("Chain.Sync", fmt.Sprintf("sync: process block %d", bd.Height), err) + return coreerr.E("Chain.Sync", core.Sprintf("sync: process block %d", bd.Height), err) } } @@ -126,7 +125,7 @@ func (c *Chain) processBlock(bd rpc.BlockDetails, opts SyncOptions) error { } txBlobBytes, err := hex.DecodeString(txInfo.Blob) if err != nil { - return coreerr.E("Chain.processBlock", fmt.Sprintf("decode tx hex %s", txInfo.ID), err) + return coreerr.E("Chain.processBlock", core.Sprintf("decode tx hex %s", txInfo.ID), err) } txBlobs = append(txBlobs, txBlobBytes) } @@ -140,7 +139,7 @@ func (c *Chain) processBlock(bd rpc.BlockDetails, opts SyncOptions) error { return coreerr.E("Chain.processBlock", "parse daemon block hash", err) } if computedHash != daemonHash { - return coreerr.E("Chain.processBlock", fmt.Sprintf("block hash mismatch: computed %s, daemon says %s", computedHash, daemonHash), nil) + return coreerr.E("Chain.processBlock", core.Sprintf("block hash mismatch: computed %s, daemon says %s", computedHash, daemonHash), nil) } return c.processBlockBlobs(blockBlob, txBlobs, bd.Height, diff, opts) @@ -168,7 +167,7 @@ func (c *Chain) processBlockBlobs(blockBlob []byte, txBlobs [][]byte, return coreerr.E("Chain.processBlockBlobs", "parse genesis hash", err) } if blockHash != genesisHash { - return coreerr.E("Chain.processBlockBlobs", fmt.Sprintf("genesis hash %s does not match expected %s", blockHash, GenesisHash), nil) + return coreerr.E("Chain.processBlockBlobs", core.Sprintf("genesis hash %s does not match expected %s", blockHash, GenesisHash), nil) } } @@ -212,27 +211,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 coreerr.E("Chain.processBlockBlobs", fmt.Sprintf("decode tx wire [%d]", i), err) + return coreerr.E("Chain.processBlockBlobs", core.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 coreerr.E("Chain.processBlockBlobs", fmt.Sprintf("validate tx %s", txHash), err) + return coreerr.E("Chain.processBlockBlobs", core.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 coreerr.E("Chain.processBlockBlobs", fmt.Sprintf("verify tx signatures %s", txHash), err) + return coreerr.E("Chain.processBlockBlobs", core.Sprintf("verify tx signatures %s", txHash), err) } } // Index outputs. gindexes, err := c.indexOutputs(txHash, &tx) if err != nil { - return coreerr.E("Chain.processBlockBlobs", fmt.Sprintf("index tx outputs %s", txHash), err) + return coreerr.E("Chain.processBlockBlobs", core.Sprintf("index tx outputs %s", txHash), err) } // Mark key images as spent. @@ -240,11 +239,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 coreerr.E("Chain.processBlockBlobs", fmt.Sprintf("mark spent %s", inp.KeyImage), err) + return coreerr.E("Chain.processBlockBlobs", core.Sprintf("mark spent %s", inp.KeyImage), err) } case types.TxInputZC: if err := c.MarkSpent(inp.KeyImage, height); err != nil { - return coreerr.E("Chain.processBlockBlobs", fmt.Sprintf("mark spent %s", inp.KeyImage), err) + return coreerr.E("Chain.processBlockBlobs", core.Sprintf("mark spent %s", inp.KeyImage), err) } } } @@ -254,7 +253,7 @@ func (c *Chain) processBlockBlobs(blockBlob []byte, txBlobs [][]byte, KeeperBlock: height, GlobalOutputIndexes: gindexes, }); err != nil { - return coreerr.E("Chain.processBlockBlobs", fmt.Sprintf("store tx %s", txHash), err) + return coreerr.E("Chain.processBlockBlobs", core.Sprintf("store tx %s", txHash), err) } } @@ -332,10 +331,10 @@ func resolveBlockBlobs(blocks []rpc.BlockDetails, client *rpc.Client) error { return coreerr.E("resolveBlockBlobs", "fetch tx blobs", err) } if len(missed) > 0 { - return coreerr.E("resolveBlockBlobs", fmt.Sprintf("daemon missed %d tx(es): %v", len(missed), missed), nil) + return coreerr.E("resolveBlockBlobs", core.Sprintf("daemon missed %d tx(es): %v", len(missed), missed), nil) } if len(txHexes) != len(allHashes) { - return coreerr.E("resolveBlockBlobs", fmt.Sprintf("expected %d tx blobs, got %d", len(allHashes), len(txHexes)), nil) + return coreerr.E("resolveBlockBlobs", core.Sprintf("expected %d tx blobs, got %d", len(allHashes), len(txHexes)), nil) } // Index fetched blobs by hash. @@ -363,16 +362,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 coreerr.E("resolveBlockBlobs", fmt.Sprintf("block %d: parse header", bd.Height), err) + return coreerr.E("resolveBlockBlobs", core.Sprintf("block %d: parse header", bd.Height), err) } // Miner tx blob is transactions_details[0]. if len(bd.Transactions) == 0 { - return coreerr.E("resolveBlockBlobs", fmt.Sprintf("block %d has no transactions_details", bd.Height), nil) + return coreerr.E("resolveBlockBlobs", core.Sprintf("block %d has no transactions_details", bd.Height), nil) } minerTxBlob, err := hex.DecodeString(bd.Transactions[0].Blob) if err != nil { - return coreerr.E("resolveBlockBlobs", fmt.Sprintf("block %d: decode miner tx hex", bd.Height), err) + return coreerr.E("resolveBlockBlobs", core.Sprintf("block %d: decode miner tx hex", bd.Height), err) } // Collect regular tx hashes. @@ -380,7 +379,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 coreerr.E("resolveBlockBlobs", fmt.Sprintf("block %d: parse tx hash %s", bd.Height, txInfo.ID), err) + return coreerr.E("resolveBlockBlobs", core.Sprintf("block %d: parse tx hash %s", bd.Height, txInfo.ID), err) } txHashes = append(txHashes, h) } @@ -414,8 +413,8 @@ func parseBlockHeader(objectInJSON string) (*types.BlockHeader, error) { } var hj blockHeaderJSON - if err := json.Unmarshal([]byte("{"+m[1]+"}"), &hj); err != nil { - return nil, coreerr.E("parseBlockHeader", "unmarshal AGGREGATED", err) + if r := core.JSONUnmarshalString(core.Concat("{", m[1], "}"), &hj); !r.OK { + return nil, coreerr.E("parseBlockHeader", "unmarshal AGGREGATED", r.Value.(error)) } prevID, err := types.HashFromHex(hj.PrevID) diff --git a/chain/validate.go b/chain/validate.go index 50c5322..34a6308 100644 --- a/chain/validate.go +++ b/chain/validate.go @@ -7,8 +7,7 @@ package chain import ( "bytes" - "fmt" - + "dappco.re/go/core" coreerr "dappco.re/go/core/log" "dappco.re/go/core/blockchain/config" @@ -26,7 +25,7 @@ func (c *Chain) ValidateHeader(b *types.Block, expectedHeight uint64) error { // Height sequence check. if expectedHeight != currentHeight { - return coreerr.E("Chain.ValidateHeader", fmt.Sprintf("validate: expected height %d but chain is at %d", expectedHeight, currentHeight), nil) + return coreerr.E("Chain.ValidateHeader", core.Sprintf("validate: expected height %d but chain is at %d", expectedHeight, currentHeight), nil) } // Genesis block: prev_id must be zero. @@ -43,7 +42,7 @@ func (c *Chain) ValidateHeader(b *types.Block, expectedHeight uint64) error { return coreerr.E("Chain.ValidateHeader", "validate: get top block", err) } if 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) + return coreerr.E("Chain.ValidateHeader", core.Sprintf("validate: prev_id %s does not match top block %s", b.PrevID, topMeta.Hash), nil) } // Block size check. @@ -51,7 +50,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 coreerr.E("Chain.ValidateHeader", fmt.Sprintf("validate: block size %d exceeds max %d", buf.Len(), config.MaxBlockSize), nil) + return coreerr.E("Chain.ValidateHeader", core.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 c2dbe7a..f762d45 100644 --- a/cmd_explorer.go +++ b/cmd_explorer.go @@ -7,11 +7,11 @@ package blockchain import ( "context" - "os" "os/signal" - "path/filepath" "sync" + "syscall" + "dappco.re/go/core" coreerr "dappco.re/go/core/log" cli "dappco.re/go/core/cli/pkg/cli" @@ -38,7 +38,7 @@ func runExplorer(dataDir, seed string, testnet bool) error { return err } - dbPath := filepath.Join(dataDir, "chain.db") + dbPath := core.JoinPath(dataDir, "chain.db") s, err := store.New(dbPath) if err != nil { return coreerr.E("runExplorer", "open store", err) @@ -48,7 +48,7 @@ func runExplorer(dataDir, seed string, testnet bool) error { c := chain.New(s) cfg, forks := resolveConfig(testnet, &seed) - ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) + ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT) defer cancel() var wg sync.WaitGroup diff --git a/cmd_sync.go b/cmd_sync.go index f184999..cb464d0 100644 --- a/cmd_sync.go +++ b/cmd_sync.go @@ -7,14 +7,12 @@ package blockchain import ( "context" - "fmt" "log" - "os" "os/signal" - "path/filepath" "sync" "syscall" + "dappco.re/go/core" coreerr "dappco.re/go/core/log" "dappco.re/go/core/blockchain/chain" @@ -55,7 +53,7 @@ func runSyncForeground(dataDir, seed string, testnet bool) error { return err } - dbPath := filepath.Join(dataDir, "chain.db") + dbPath := core.JoinPath(dataDir, "chain.db") s, err := store.New(dbPath) if err != nil { return coreerr.E("runSyncForeground", "open store", err) @@ -65,7 +63,7 @@ func runSyncForeground(dataDir, seed string, testnet bool) error { c := chain.New(s) cfg, forks := resolveConfig(testnet, &seed) - ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer cancel() log.Println("Starting headless P2P sync...") @@ -79,7 +77,7 @@ func runSyncDaemon(dataDir, seed string, testnet bool) error { return err } - pidFile := filepath.Join(dataDir, "sync.pid") + pidFile := core.JoinPath(dataDir, "sync.pid") d := process.NewDaemon(process.DaemonOptions{ PIDFile: pidFile, @@ -94,7 +92,7 @@ func runSyncDaemon(dataDir, seed string, testnet bool) error { return coreerr.E("runSyncDaemon", "daemon start", err) } - dbPath := filepath.Join(dataDir, "chain.db") + dbPath := core.JoinPath(dataDir, "chain.db") s, err := store.New(dbPath) if err != nil { _ = d.Stop() @@ -105,7 +103,7 @@ func runSyncDaemon(dataDir, seed string, testnet bool) error { c := chain.New(s) cfg, forks := resolveConfig(testnet, &seed) - ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer cancel() d.SetReady(true) @@ -124,19 +122,14 @@ func runSyncDaemon(dataDir, seed string, testnet bool) error { } func stopSyncDaemon(dataDir string) error { - pidFile := filepath.Join(dataDir, "sync.pid") + pidFile := core.JoinPath(dataDir, "sync.pid") pid, running := process.ReadPID(pidFile) if pid == 0 || !running { return coreerr.E("stopSyncDaemon", "no running sync daemon found", nil) } - proc, err := os.FindProcess(pid) - if err != nil { - return coreerr.E("stopSyncDaemon", fmt.Sprintf("find process %d", pid), err) - } - - if err := proc.Signal(syscall.SIGTERM); err != nil { - return coreerr.E("stopSyncDaemon", fmt.Sprintf("signal process %d", pid), err) + if err := syscall.Kill(pid, syscall.SIGTERM); err != nil { + return coreerr.E("stopSyncDaemon", core.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 c7a0f4b..269dfed 100644 --- a/commands.go +++ b/commands.go @@ -6,9 +6,7 @@ package blockchain import ( - "os" - "path/filepath" - + "dappco.re/go/core" coreio "dappco.re/go/core/io" coreerr "dappco.re/go/core/log" @@ -54,11 +52,11 @@ func resolveConfig(testnet bool, seed *string) (config.ChainConfig, []config.Har } func defaultDataDir() string { - home, err := os.UserHomeDir() - if err != nil { + home := core.Env("DIR_HOME") + if home == "" { return ".lethean" } - return filepath.Join(home, ".lethean", "chain") + return core.JoinPath(home, ".lethean", "chain") } func ensureDataDir(dataDir string) error { diff --git a/consensus/block.go b/consensus/block.go index f34b4f8..71878bc 100644 --- a/consensus/block.go +++ b/consensus/block.go @@ -6,9 +6,9 @@ package consensus import ( - "fmt" "slices" + "dappco.re/go/core" coreerr "dappco.re/go/core/log" "dappco.re/go/core/blockchain/config" @@ -30,7 +30,7 @@ func CheckTimestamp(blockTimestamp uint64, flags uint8, adjustedTime uint64, rec limit = config.PosBlockFutureTimeLimit } if blockTimestamp > adjustedTime+limit { - return coreerr.E("CheckTimestamp", fmt.Sprintf("%d > %d + %d", + return coreerr.E("CheckTimestamp", core.Sprintf("%d > %d + %d", blockTimestamp, adjustedTime, limit), ErrTimestampFuture) } @@ -41,7 +41,7 @@ func CheckTimestamp(blockTimestamp uint64, flags uint8, adjustedTime uint64, rec median := medianTimestamp(recentTimestamps) if blockTimestamp < median { - return coreerr.E("CheckTimestamp", fmt.Sprintf("%d < median %d", + return coreerr.E("CheckTimestamp", core.Sprintf("%d < median %d", blockTimestamp, median), ErrTimestampOld) } @@ -75,7 +75,7 @@ func ValidateMinerTx(tx *types.Transaction, height uint64, forks []config.HardFo return coreerr.E("ValidateMinerTx", "first input is not txin_gen", ErrMinerTxInputs) } if gen.Height != height { - return coreerr.E("ValidateMinerTx", fmt.Sprintf("got %d, expected %d", gen.Height, height), ErrMinerTxHeight) + return coreerr.E("ValidateMinerTx", core.Sprintf("got %d, expected %d", gen.Height, height), ErrMinerTxHeight) } // PoW blocks: exactly 1 input. PoS: exactly 2. @@ -94,7 +94,7 @@ func ValidateMinerTx(tx *types.Transaction, height uint64, forks []config.HardFo // Post-HF4: accept ZC inputs. } } else { - return coreerr.E("ValidateMinerTx", fmt.Sprintf("%d inputs (expected 1 or 2)", len(tx.Vin)), ErrMinerTxInputs) + return coreerr.E("ValidateMinerTx", core.Sprintf("%d inputs (expected 1 or 2)", len(tx.Vin)), ErrMinerTxInputs) } return nil @@ -121,7 +121,7 @@ func ValidateBlockReward(minerTx *types.Transaction, height, blockSize, medianSi } if outputSum > expected { - return coreerr.E("ValidateBlockReward", fmt.Sprintf("outputs %d > expected %d", outputSum, expected), ErrRewardMismatch) + return coreerr.E("ValidateBlockReward", core.Sprintf("outputs %d > expected %d", outputSum, expected), ErrRewardMismatch) } return nil @@ -152,7 +152,7 @@ func expectedBlockMajorVersion(forks []config.HardFork, height uint64) uint8 { func checkBlockVersion(blk *types.Block, forks []config.HardFork, height uint64) error { expected := expectedBlockMajorVersion(forks, height) if blk.MajorVersion != expected { - return coreerr.E("checkBlockVersion", fmt.Sprintf("got %d, want %d at height %d", + return coreerr.E("checkBlockVersion", core.Sprintf("got %d, want %d at height %d", blk.MajorVersion, expected, height), ErrBlockMajorVersion) } return nil @@ -226,7 +226,7 @@ func IsPreHardforkFreeze(forks []config.HardFork, version uint8, height uint64) func ValidateTransactionInBlock(tx *types.Transaction, txBlob []byte, forks []config.HardFork, height uint64) error { // Pre-hardfork freeze: reject non-coinbase transactions in the freeze window. if !isCoinbase(tx) && IsPreHardforkFreeze(forks, config.HF5, height) { - return coreerr.E("ValidateTransactionInBlock", fmt.Sprintf("height %d is within HF5 freeze window", height), ErrPreHardforkFreeze) + return coreerr.E("ValidateTransactionInBlock", core.Sprintf("height %d is within HF5 freeze window", height), ErrPreHardforkFreeze) } return ValidateTransaction(tx, txBlob, forks, height) diff --git a/consensus/errors.go b/consensus/errors.go index 181feb4..06cacb1 100644 --- a/consensus/errors.go +++ b/consensus/errors.go @@ -5,39 +5,39 @@ package consensus -import "errors" +import "dappco.re/go/core" // Sentinel errors for consensus validation failures. var ( // Transaction structural errors. - ErrTxTooLarge = errors.New("consensus: transaction too large") - ErrNoInputs = errors.New("consensus: transaction has no inputs") - ErrTooManyInputs = errors.New("consensus: transaction exceeds max inputs") - ErrInvalidInputType = errors.New("consensus: unsupported input type") - ErrNoOutputs = errors.New("consensus: transaction has no outputs") - ErrTooFewOutputs = errors.New("consensus: transaction below min outputs") - ErrTooManyOutputs = errors.New("consensus: transaction exceeds max outputs") - ErrInvalidOutput = errors.New("consensus: invalid output") - ErrDuplicateKeyImage = errors.New("consensus: duplicate key image in transaction") - ErrInvalidExtra = errors.New("consensus: invalid extra field") - ErrTxVersionInvalid = errors.New("consensus: invalid transaction version for current hardfork") - ErrPreHardforkFreeze = errors.New("consensus: non-coinbase transaction rejected during pre-hardfork freeze") + ErrTxTooLarge = core.E("", "consensus: transaction too large", nil) + ErrNoInputs = core.E("", "consensus: transaction has no inputs", nil) + ErrTooManyInputs = core.E("", "consensus: transaction exceeds max inputs", nil) + ErrInvalidInputType = core.E("", "consensus: unsupported input type", nil) + ErrNoOutputs = core.E("", "consensus: transaction has no outputs", nil) + ErrTooFewOutputs = core.E("", "consensus: transaction below min outputs", nil) + ErrTooManyOutputs = core.E("", "consensus: transaction exceeds max outputs", nil) + ErrInvalidOutput = core.E("", "consensus: invalid output", nil) + ErrDuplicateKeyImage = core.E("", "consensus: duplicate key image in transaction", nil) + ErrInvalidExtra = core.E("", "consensus: invalid extra field", nil) + ErrTxVersionInvalid = core.E("", "consensus: invalid transaction version for current hardfork", nil) + ErrPreHardforkFreeze = core.E("", "consensus: non-coinbase transaction rejected during pre-hardfork freeze", nil) // Transaction economic errors. - ErrInputOverflow = errors.New("consensus: input amount overflow") - ErrOutputOverflow = errors.New("consensus: output amount overflow") - ErrNegativeFee = errors.New("consensus: outputs exceed inputs") + ErrInputOverflow = core.E("", "consensus: input amount overflow", nil) + ErrOutputOverflow = core.E("", "consensus: output amount overflow", nil) + ErrNegativeFee = core.E("", "consensus: outputs exceed inputs", nil) // Block errors. - ErrBlockTooLarge = errors.New("consensus: block exceeds max size") - ErrBlockMajorVersion = errors.New("consensus: invalid block major version for height") - ErrTimestampFuture = errors.New("consensus: block timestamp too far in future") - ErrTimestampOld = errors.New("consensus: block timestamp below median") - ErrMinerTxInputs = errors.New("consensus: invalid miner transaction inputs") - ErrMinerTxHeight = errors.New("consensus: miner transaction height mismatch") - ErrMinerTxUnlock = errors.New("consensus: miner transaction unlock time invalid") - ErrRewardMismatch = errors.New("consensus: block reward mismatch") - ErrMinerTxProofs = errors.New("consensus: miner transaction proof count invalid") + ErrBlockTooLarge = core.E("", "consensus: block exceeds max size", nil) + ErrBlockMajorVersion = core.E("", "consensus: invalid block major version for height", nil) + ErrTimestampFuture = core.E("", "consensus: block timestamp too far in future", nil) + ErrTimestampOld = core.E("", "consensus: block timestamp below median", nil) + ErrMinerTxInputs = core.E("", "consensus: invalid miner transaction inputs", nil) + ErrMinerTxHeight = core.E("", "consensus: miner transaction height mismatch", nil) + ErrMinerTxUnlock = core.E("", "consensus: miner transaction unlock time invalid", nil) + ErrRewardMismatch = core.E("", "consensus: block reward mismatch", nil) + ErrMinerTxProofs = core.E("", "consensus: miner transaction proof count invalid", nil) // ErrBlockVersion is an alias for ErrBlockMajorVersion, used by // checkBlockVersion when the block major version does not match diff --git a/consensus/fee.go b/consensus/fee.go index b6f5f7d..7f5620a 100644 --- a/consensus/fee.go +++ b/consensus/fee.go @@ -6,9 +6,9 @@ package consensus import ( - "fmt" "math" + "dappco.re/go/core" coreerr "dappco.re/go/core/log" "dappco.re/go/core/blockchain/types" @@ -33,7 +33,7 @@ func TxFee(tx *types.Transaction) (uint64, error) { } if outputSum > inputSum { - return 0, coreerr.E("TxFee", fmt.Sprintf("inputs=%d, outputs=%d", inputSum, outputSum), ErrNegativeFee) + return 0, coreerr.E("TxFee", core.Sprintf("inputs=%d, outputs=%d", inputSum, outputSum), ErrNegativeFee) } return inputSum - outputSum, nil diff --git a/consensus/reward.go b/consensus/reward.go index f76e7b0..f121314 100644 --- a/consensus/reward.go +++ b/consensus/reward.go @@ -6,9 +6,9 @@ package consensus import ( - "fmt" "math/bits" + "dappco.re/go/core" coreerr "dappco.re/go/core/log" "dappco.re/go/core/blockchain/config" @@ -44,7 +44,7 @@ func BlockReward(baseReward, blockSize, medianSize uint64) (uint64, error) { } if blockSize > 2*effectiveMedian { - return 0, coreerr.E("BlockReward", fmt.Sprintf("consensus: block size %d too large for median %d", blockSize, effectiveMedian), nil) + return 0, coreerr.E("BlockReward", core.Sprintf("consensus: block size %d too large for median %d", blockSize, effectiveMedian), nil) } // penalty = baseReward * (2*median - size) * size / median² diff --git a/consensus/tx.go b/consensus/tx.go index 17ba9ea..e226077 100644 --- a/consensus/tx.go +++ b/consensus/tx.go @@ -6,8 +6,7 @@ package consensus import ( - "fmt" - + "dappco.re/go/core" coreerr "dappco.re/go/core/log" "dappco.re/go/core/blockchain/config" @@ -26,7 +25,7 @@ func ValidateTransaction(tx *types.Transaction, txBlob []byte, forks []config.Ha // 1. Blob size. if uint64(len(txBlob)) >= config.MaxTransactionBlobSize { - return coreerr.E("ValidateTransaction", fmt.Sprintf("%d bytes", len(txBlob)), ErrTxTooLarge) + return coreerr.E("ValidateTransaction", core.Sprintf("%d bytes", len(txBlob)), ErrTxTooLarge) } // 2. Input count. @@ -34,7 +33,7 @@ func ValidateTransaction(tx *types.Transaction, txBlob []byte, forks []config.Ha return ErrNoInputs } if uint64(len(tx.Vin)) > config.TxMaxAllowedInputs { - return coreerr.E("ValidateTransaction", fmt.Sprintf("%d", len(tx.Vin)), ErrTooManyInputs) + return coreerr.E("ValidateTransaction", core.Sprintf("%d", len(tx.Vin)), ErrTooManyInputs) } hf1Active := config.IsHardForkActive(forks, config.HF1, height) @@ -82,13 +81,13 @@ func checkTxVersion(tx *types.Transaction, forks []config.HardFork, height uint6 if hf5Active && tx.Version < types.VersionPostHF5 { return coreerr.E("checkTxVersion", - fmt.Sprintf("version %d too low after HF5 at height %d", tx.Version, height), + core.Sprintf("version %d too low after HF5 at height %d", tx.Version, height), ErrTxVersionInvalid) } if !hf5Active && tx.Version >= types.VersionPostHF5 { return coreerr.E("checkTxVersion", - fmt.Sprintf("version %d not allowed before HF5 at height %d", tx.Version, height), + core.Sprintf("version %d not allowed before HF5 at height %d", tx.Version, height), ErrTxVersionInvalid) } @@ -105,12 +104,12 @@ func checkInputTypes(tx *types.Transaction, hf1Active, hf4Active bool) error { case types.TxInputHTLC, types.TxInputMultisig: // HTLC and multisig inputs require at least HF1. if !hf1Active { - return coreerr.E("checkInputTypes", fmt.Sprintf("tag %d pre-HF1", vin.InputType()), ErrInvalidInputType) + return coreerr.E("checkInputTypes", core.Sprintf("tag %d pre-HF1", vin.InputType()), ErrInvalidInputType) } default: // Future types (ZC) — accept if HF4+. if !hf4Active { - return coreerr.E("checkInputTypes", fmt.Sprintf("tag %d pre-HF4", vin.InputType()), ErrInvalidInputType) + return coreerr.E("checkInputTypes", core.Sprintf("tag %d pre-HF4", vin.InputType()), ErrInvalidInputType) } } } @@ -123,24 +122,24 @@ func checkOutputs(tx *types.Transaction, hf1Active, hf4Active bool) error { } if hf4Active && uint64(len(tx.Vout)) < config.TxMinAllowedOutputs { - return coreerr.E("checkOutputs", fmt.Sprintf("%d (min %d)", len(tx.Vout), config.TxMinAllowedOutputs), ErrTooFewOutputs) + return coreerr.E("checkOutputs", core.Sprintf("%d (min %d)", len(tx.Vout), config.TxMinAllowedOutputs), ErrTooFewOutputs) } if uint64(len(tx.Vout)) > config.TxMaxAllowedOutputs { - return coreerr.E("checkOutputs", fmt.Sprintf("%d", len(tx.Vout)), ErrTooManyOutputs) + return coreerr.E("checkOutputs", core.Sprintf("%d", len(tx.Vout)), ErrTooManyOutputs) } for i, vout := range tx.Vout { switch o := vout.(type) { case types.TxOutputBare: if o.Amount == 0 { - return coreerr.E("checkOutputs", fmt.Sprintf("output %d has zero amount", i), ErrInvalidOutput) + return coreerr.E("checkOutputs", core.Sprintf("output %d has zero amount", i), ErrInvalidOutput) } // HTLC and Multisig output targets require at least HF1. switch o.Target.(type) { case types.TxOutHTLC, types.TxOutMultisig: if !hf1Active { - return coreerr.E("checkOutputs", fmt.Sprintf("output %d: HTLC/multisig target pre-HF1", i), ErrInvalidOutput) + return coreerr.E("checkOutputs", core.Sprintf("output %d: HTLC/multisig target pre-HF1", i), ErrInvalidOutput) } } case types.TxOutputZarcanum: diff --git a/consensus/v2sig.go b/consensus/v2sig.go index c836358..9ed09cb 100644 --- a/consensus/v2sig.go +++ b/consensus/v2sig.go @@ -7,8 +7,7 @@ package consensus import ( "bytes" - "fmt" - + "dappco.re/go/core" coreerr "dappco.re/go/core/log" "dappco.re/go/core/blockchain/types" @@ -47,7 +46,7 @@ func parseV2Signatures(raw []byte) ([]v2SigEntry, error) { for i := uint64(0); i < count; i++ { tag := dec.ReadUint8() if dec.Err() != nil { - return nil, coreerr.E("parseV2Signatures", fmt.Sprintf("read sig tag %d", i), dec.Err()) + return nil, coreerr.E("parseV2Signatures", core.Sprintf("read sig tag %d", i), dec.Err()) } entry := v2SigEntry{tag: tag} @@ -56,7 +55,7 @@ func parseV2Signatures(raw []byte) ([]v2SigEntry, error) { case types.SigTypeZC: zc, err := parseZCSig(dec) if err != nil { - return nil, coreerr.E("parseV2Signatures", fmt.Sprintf("parse ZC_sig %d", i), err) + return nil, coreerr.E("parseV2Signatures", core.Sprintf("parse ZC_sig %d", i), err) } entry.zcSig = zc @@ -76,11 +75,11 @@ func parseV2Signatures(raw []byte) ([]v2SigEntry, error) { skipZarcanumSig(dec) default: - return nil, coreerr.E("parseV2Signatures", fmt.Sprintf("unsupported sig tag 0x%02x", tag), nil) + return nil, coreerr.E("parseV2Signatures", core.Sprintf("unsupported sig tag 0x%02x", tag), nil) } if dec.Err() != nil { - return nil, coreerr.E("parseV2Signatures", fmt.Sprintf("parse sig %d (tag 0x%02x)", i, tag), dec.Err()) + return nil, coreerr.E("parseV2Signatures", core.Sprintf("parse sig %d (tag 0x%02x)", i, tag), dec.Err()) } entries = append(entries, entry) } @@ -119,7 +118,7 @@ func parseZCSig(dec *wire.Decoder) (*zcSigData, error) { } if rgCount != rxCount { - return nil, coreerr.E("parseZCSig", fmt.Sprintf("CLSAG r_g count %d != r_x count %d", rgCount, rxCount), nil) + return nil, coreerr.E("parseZCSig", core.Sprintf("CLSAG r_g count %d != r_x count %d", rgCount, rxCount), nil) } zc.ringSize = int(rgCount) @@ -205,7 +204,7 @@ func parseV2Proofs(raw []byte) (*v2ProofData, error) { for i := uint64(0); i < count; i++ { tag := dec.ReadUint8() if dec.Err() != nil { - return nil, coreerr.E("parseV2Proofs", fmt.Sprintf("read proof tag %d", i), dec.Err()) + return nil, coreerr.E("parseV2Proofs", core.Sprintf("read proof tag %d", i), dec.Err()) } switch tag { @@ -218,7 +217,7 @@ func parseV2Proofs(raw []byte) (*v2ProofData, error) { for j := uint64(0); j < nBGE; j++ { data.bgeProofs[j] = readBGEProofBytes(dec) if dec.Err() != nil { - return nil, coreerr.E("parseV2Proofs", fmt.Sprintf("parse BGE proof %d", j), dec.Err()) + return nil, coreerr.E("parseV2Proofs", core.Sprintf("parse BGE proof %d", j), dec.Err()) } } @@ -239,7 +238,7 @@ func parseV2Proofs(raw []byte) (*v2ProofData, error) { } default: - return nil, coreerr.E("parseV2Proofs", fmt.Sprintf("unsupported proof tag 0x%02x", tag), nil) + return nil, coreerr.E("parseV2Proofs", core.Sprintf("unsupported proof tag 0x%02x", tag), nil) } } diff --git a/consensus/verify.go b/consensus/verify.go index 1438aba..ce7cec2 100644 --- a/consensus/verify.go +++ b/consensus/verify.go @@ -6,8 +6,7 @@ package consensus import ( - "fmt" - + "dappco.re/go/core" coreerr "dappco.re/go/core/log" "dappco.re/go/core/blockchain/config" @@ -71,7 +70,7 @@ func verifyV1Signatures(tx *types.Transaction, getRingOutputs RingOutputsFn) err } if len(tx.Signatures) != ringInputCount { - return coreerr.E("verifyV1Signatures", fmt.Sprintf("consensus: signature count %d != input count %d", len(tx.Signatures), ringInputCount), nil) + return coreerr.E("verifyV1Signatures", core.Sprintf("consensus: signature count %d != input count %d", len(tx.Signatures), ringInputCount), nil) } // Actual NLSAG verification requires the crypto bridge and ring outputs. @@ -110,12 +109,12 @@ func verifyV1Signatures(tx *types.Transaction, getRingOutputs RingOutputsFn) err ringKeys, err := getRingOutputs(amount, offsets) if err != nil { - return coreerr.E("verifyV1Signatures", fmt.Sprintf("consensus: failed to fetch ring outputs for input %d", sigIdx), err) + return coreerr.E("verifyV1Signatures", core.Sprintf("consensus: failed to fetch ring outputs for input %d", sigIdx), err) } ringSigs := tx.Signatures[sigIdx] if 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) + return coreerr.E("verifyV1Signatures", core.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. @@ -130,7 +129,7 @@ func verifyV1Signatures(tx *types.Transaction, getRingOutputs RingOutputsFn) err } if !crypto.CheckRingSignature([32]byte(prefixHash), [32]byte(keyImage), pubs, sigs) { - return coreerr.E("verifyV1Signatures", fmt.Sprintf("consensus: ring signature verification failed for input %d", sigIdx), nil) + return coreerr.E("verifyV1Signatures", core.Sprintf("consensus: ring signature verification failed for input %d", sigIdx), nil) } sigIdx++ @@ -149,7 +148,7 @@ func verifyV2Signatures(tx *types.Transaction, getZCRingOutputs ZCRingOutputsFn) // Match signatures to inputs: each input must have a corresponding signature. if 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) + return coreerr.E("verifyV2Signatures", core.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. @@ -157,11 +156,11 @@ func verifyV2Signatures(tx *types.Transaction, getZCRingOutputs ZCRingOutputsFn) switch vin.(type) { case types.TxInputZC: if sigEntries[i].tag != types.SigTypeZC { - return coreerr.E("verifyV2Signatures", fmt.Sprintf("consensus: input %d is ZC but signature tag is 0x%02x", i, sigEntries[i].tag), nil) + return coreerr.E("verifyV2Signatures", core.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 coreerr.E("verifyV2Signatures", fmt.Sprintf("consensus: input %d is to_key but signature tag is 0x%02x", i, sigEntries[i].tag), nil) + return coreerr.E("verifyV2Signatures", core.Sprintf("consensus: input %d is to_key but signature tag is 0x%02x", i, sigEntries[i].tag), nil) } } } @@ -185,7 +184,7 @@ func verifyV2Signatures(tx *types.Transaction, getZCRingOutputs ZCRingOutputsFn) zc := sigEntries[i].zcSig if zc == nil { - return coreerr.E("verifyV2Signatures", fmt.Sprintf("consensus: input %d: missing ZC_sig data", i), nil) + return coreerr.E("verifyV2Signatures", core.Sprintf("consensus: input %d: missing ZC_sig data", i), nil) } // Extract absolute global indices from key offsets. @@ -196,11 +195,11 @@ func verifyV2Signatures(tx *types.Transaction, getZCRingOutputs ZCRingOutputsFn) ringMembers, err := getZCRingOutputs(offsets) if err != nil { - return coreerr.E("verifyV2Signatures", fmt.Sprintf("consensus: failed to fetch ZC ring outputs for input %d", i), err) + return coreerr.E("verifyV2Signatures", core.Sprintf("consensus: failed to fetch ZC ring outputs for input %d", i), err) } if 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) + return coreerr.E("verifyV2Signatures", core.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. @@ -219,7 +218,7 @@ func verifyV2Signatures(tx *types.Transaction, getZCRingOutputs ZCRingOutputsFn) [32]byte(zcIn.KeyImage), zc.clsagFlatSig, ) { - return coreerr.E("verifyV2Signatures", fmt.Sprintf("consensus: CLSAG GGX verification failed for input %d", i), nil) + return coreerr.E("verifyV2Signatures", core.Sprintf("consensus: CLSAG GGX verification failed for input %d", i), nil) } } @@ -273,7 +272,7 @@ func verifyBGEProofs(tx *types.Transaction, sigEntries []v2SigEntry, } if 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) + return coreerr.E("verifyBGEProofs", core.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. @@ -289,7 +288,7 @@ func verifyBGEProofs(tx *types.Transaction, sigEntries []v2SigEntry, for i, p := range pseudoOutAssetIDs { full, err := crypto.PointMul8(p) if err != nil { - return coreerr.E("verifyBGEProofs", fmt.Sprintf("consensus: mul8 pseudo-out asset ID %d", i), err) + return coreerr.E("verifyBGEProofs", core.Sprintf("consensus: mul8 pseudo-out asset ID %d", i), err) } mul8PseudoOuts[i] = full } @@ -300,7 +299,7 @@ func verifyBGEProofs(tx *types.Transaction, sigEntries []v2SigEntry, // mul8 the output's blinded asset ID. mul8Out, err := crypto.PointMul8(outAssetID) if err != nil { - return coreerr.E("verifyBGEProofs", fmt.Sprintf("consensus: mul8 output asset ID %d", j), err) + return coreerr.E("verifyBGEProofs", core.Sprintf("consensus: mul8 output asset ID %d", j), err) } // ring[i] = mul8(pseudo_out_i) - mul8(output_j) @@ -308,13 +307,13 @@ func verifyBGEProofs(tx *types.Transaction, sigEntries []v2SigEntry, for i, mul8Pseudo := range mul8PseudoOuts { diff, err := crypto.PointSub(mul8Pseudo, mul8Out) if err != nil { - return coreerr.E("verifyBGEProofs", fmt.Sprintf("consensus: BGE ring[%d][%d] sub", j, i), err) + return coreerr.E("verifyBGEProofs", core.Sprintf("consensus: BGE ring[%d][%d] sub", j, i), err) } ring[i] = diff } if !crypto.VerifyBGE(context, ring, proofs.bgeProofs[j]) { - return coreerr.E("verifyBGEProofs", fmt.Sprintf("consensus: BGE proof verification failed for output %d", j), nil) + return coreerr.E("verifyBGEProofs", core.Sprintf("consensus: BGE proof verification failed for output %d", j), nil) } } diff --git a/crypto/keygen.go b/crypto/keygen.go index 110c445..fce45a0 100644 --- a/crypto/keygen.go +++ b/crypto/keygen.go @@ -8,9 +8,9 @@ package crypto import "C" import ( - "fmt" "unsafe" + "dappco.re/go/core" coreerr "dappco.re/go/core/log" ) @@ -21,7 +21,7 @@ func GenerateKeys() (pub [32]byte, sec [32]byte, err error) { (*C.uint8_t)(unsafe.Pointer(&sec[0])), ) if rc != 0 { - err = coreerr.E("GenerateKeys", fmt.Sprintf("generate_keys failed (rc=%d)", rc), nil) + err = coreerr.E("GenerateKeys", core.Sprintf("generate_keys failed (rc=%d)", rc), nil) } return } @@ -34,7 +34,7 @@ func SecretToPublic(sec [32]byte) ([32]byte, error) { (*C.uint8_t)(unsafe.Pointer(&pub[0])), ) if rc != 0 { - return pub, coreerr.E("SecretToPublic", fmt.Sprintf("secret_to_public failed (rc=%d)", rc), nil) + return pub, coreerr.E("SecretToPublic", core.Sprintf("secret_to_public failed (rc=%d)", rc), nil) } return pub, nil } diff --git a/crypto/pow.go b/crypto/pow.go index 18c0efe..0aa0345 100644 --- a/crypto/pow.go +++ b/crypto/pow.go @@ -8,9 +8,9 @@ package crypto // #include "bridge.h" import "C" import ( - "fmt" "unsafe" + "dappco.re/go/core" coreerr "dappco.re/go/core/log" ) @@ -25,7 +25,7 @@ func RandomXHash(key, input []byte) ([32]byte, error) { (*C.uint8_t)(unsafe.Pointer(&output[0])), ) if ret != 0 { - return output, coreerr.E("RandomXHash", fmt.Sprintf("RandomX hash failed with code %d", ret), nil) + return output, coreerr.E("RandomXHash", core.Sprintf("RandomX hash failed with code %d", ret), nil) } return output, nil } diff --git a/go.mod b/go.mod index 27e9447..57aac66 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module dappco.re/go/core/blockchain go 1.26.0 require ( + dappco.re/go/core v0.8.0-alpha.1 dappco.re/go/core/cli v0.3.1 dappco.re/go/core/io v0.2.0 dappco.re/go/core/log v0.1.0 @@ -63,7 +64,7 @@ require ( ) replace ( - dappco.re/go/core => forge.lthn.ai/core/go v0.5.0 + dappco.re/go/core => forge.lthn.ai/core/go v0.8.0-alpha.1 dappco.re/go/core/cli => forge.lthn.ai/core/cli v0.3.1 dappco.re/go/core/crypt => forge.lthn.ai/core/go-crypt v0.1.7 dappco.re/go/core/i18n => forge.lthn.ai/core/go-i18n v0.1.4 diff --git a/go.sum b/go.sum index 640b656..87ec313 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ forge.lthn.ai/core/cli v0.3.1 h1:ZpHhaDrdbaV98JDxj/f0E5nytYk9tTMRu3qohGyK4M0= forge.lthn.ai/core/cli v0.3.1/go.mod h1:28cOl9eK0H033Otkjrv9f/QCmtHcJl+IIx4om8JskOg= forge.lthn.ai/core/go v0.3.1 h1:5FMTsUhLcxSr07F9q3uG0Goy4zq4eLivoqi8shSY4UM= forge.lthn.ai/core/go v0.3.1/go.mod h1:gE6c8h+PJ2287qNhVUJ5SOe1kopEwHEquvinstpuyJc= +forge.lthn.ai/core/go v0.8.0-alpha.1 h1:dybLUTR9HMqtT5lxolAUaF1c+j/vGPIDMyiMygyGlq8= +forge.lthn.ai/core/go v0.8.0-alpha.1/go.mod h1:f2/tBZ3+3IqDrg2F5F598llv0nmb/4gJVCFzM5geE4A= forge.lthn.ai/core/go-crypt v0.1.6 h1:jB7L/28S1NR+91u3GcOYuKfBLzPhhBUY1fRe6WkGVns= forge.lthn.ai/core/go-crypt v0.1.6/go.mod h1:4VZAGqxlbadhSB66sJkdj54/HSJ+bSxVgwWK5kMMYDo= forge.lthn.ai/core/go-i18n v0.1.4 h1:zOHUUJDgRo88/3tj++kN+VELg/buyZ4T2OSdG3HBbLQ= diff --git a/mining/miner.go b/mining/miner.go index 8583308..918e4a2 100644 --- a/mining/miner.go +++ b/mining/miner.go @@ -10,11 +10,11 @@ import ( "context" "encoding/binary" "encoding/hex" - "fmt" "strconv" "sync/atomic" "time" + "dappco.re/go/core" coreerr "dappco.re/go/core/log" "dappco.re/go/core/blockchain/consensus" @@ -141,7 +141,7 @@ func (m *Miner) Start(ctx context.Context) error { // Parse difficulty. diff, err := strconv.ParseUint(tmpl.Difficulty, 10, 64) if err != nil { - return coreerr.E("Miner.Start", fmt.Sprintf("mining: invalid difficulty %q", tmpl.Difficulty), err) + return coreerr.E("Miner.Start", core.Sprintf("mining: invalid difficulty %q", tmpl.Difficulty), err) } // Decode the block template blob. diff --git a/rpc/blocks.go b/rpc/blocks.go index dc5184d..26d4015 100644 --- a/rpc/blocks.go +++ b/rpc/blocks.go @@ -6,8 +6,7 @@ package rpc import ( - "fmt" - + "dappco.re/go/core" coreerr "dappco.re/go/core/log" ) @@ -21,7 +20,7 @@ func (c *Client) GetLastBlockHeader() (*BlockHeader, error) { return nil, err } if resp.Status != "OK" { - return nil, coreerr.E("Client.GetLastBlockHeader", fmt.Sprintf("getlastblockheader: status %q", resp.Status), nil) + return nil, coreerr.E("Client.GetLastBlockHeader", core.Sprintf("getlastblockheader: status %q", resp.Status), nil) } return &resp.BlockHeader, nil } @@ -39,7 +38,7 @@ func (c *Client) GetBlockHeaderByHeight(height uint64) (*BlockHeader, error) { return nil, err } if resp.Status != "OK" { - return nil, coreerr.E("Client.GetBlockHeaderByHeight", fmt.Sprintf("getblockheaderbyheight: status %q", resp.Status), nil) + return nil, coreerr.E("Client.GetBlockHeaderByHeight", core.Sprintf("getblockheaderbyheight: status %q", resp.Status), nil) } return &resp.BlockHeader, nil } @@ -57,7 +56,7 @@ func (c *Client) GetBlockHeaderByHash(hash string) (*BlockHeader, error) { return nil, err } if resp.Status != "OK" { - return nil, coreerr.E("Client.GetBlockHeaderByHash", fmt.Sprintf("getblockheaderbyhash: status %q", resp.Status), nil) + return nil, coreerr.E("Client.GetBlockHeaderByHash", core.Sprintf("getblockheaderbyhash: status %q", resp.Status), nil) } return &resp.BlockHeader, nil } @@ -77,7 +76,7 @@ func (c *Client) GetBlocksDetails(heightStart, count uint64) ([]BlockDetails, er return nil, err } if resp.Status != "OK" { - return nil, coreerr.E("Client.GetBlocksDetails", fmt.Sprintf("get_blocks_details: status %q", resp.Status), nil) + return nil, coreerr.E("Client.GetBlocksDetails", core.Sprintf("get_blocks_details: status %q", resp.Status), nil) } return resp.Blocks, nil } diff --git a/rpc/blocks_test.go b/rpc/blocks_test.go index 8104074..85c92f3 100644 --- a/rpc/blocks_test.go +++ b/rpc/blocks_test.go @@ -29,8 +29,8 @@ var testBlockHeaderJSON = `{ func blockHeaderResponse() jsonRPCResponse { return jsonRPCResponse{ JSONRPC: "2.0", - ID: json.RawMessage(`"0"`), - Result: json.RawMessage(`{"block_header":` + testBlockHeaderJSON + `,"status":"OK"}`), + ID: rawJSON(`"0"`), + Result: rawJSON(`{"block_header":` + testBlockHeaderJSON + `,"status":"OK"}`), } } @@ -96,8 +96,8 @@ func TestGetBlocksDetails_Good(t *testing.T) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(jsonRPCResponse{ JSONRPC: "2.0", - ID: json.RawMessage(`"0"`), - Result: json.RawMessage(`{ + ID: rawJSON(`"0"`), + Result: rawJSON(`{ "blocks": [{ "height": 0, "timestamp": 1770897600, @@ -134,7 +134,7 @@ func TestGetBlockHeaderByHeight_Bad_TooBig(t *testing.T) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(jsonRPCResponse{ JSONRPC: "2.0", - ID: json.RawMessage(`"0"`), + ID: rawJSON(`"0"`), Error: &jsonRPCError{Code: -2, Message: "TOO_BIG_HEIGHT"}, }) })) diff --git a/rpc/client.go b/rpc/client.go index 32ad0e2..63b613f 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -8,13 +8,12 @@ package rpc import ( "bytes" - "encoding/json" - "fmt" "io" "net/http" "net/url" "time" + "dappco.re/go/core" coreerr "dappco.re/go/core/log" ) @@ -38,7 +37,7 @@ func NewClientWithHTTP(daemonURL string, httpClient *http.Client) *Client { // Fall through with raw URL. return &Client{url: daemonURL + "/json_rpc", baseURL: daemonURL, httpClient: httpClient} } - baseURL := fmt.Sprintf("%s://%s", u.Scheme, u.Host) + baseURL := core.Sprintf("%s://%s", u.Scheme, u.Host) if u.Path == "" || u.Path == "/" { u.Path = "/json_rpc" } @@ -56,7 +55,7 @@ type RPCError struct { } func (e *RPCError) Error() string { - return fmt.Sprintf("rpc error %d: %s", e.Code, e.Message) + return core.Sprintf("rpc error %d: %s", e.Code, e.Message) } // JSON-RPC 2.0 envelope types. @@ -68,10 +67,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 rawJSON `json:"id"` + Result rawJSON `json:"result"` + Error *jsonRPCError `json:"error,omitempty"` } type jsonRPCError struct { @@ -79,26 +78,37 @@ type jsonRPCError struct { Message string `json:"message"` } +type rawJSON []byte + +func (r *rawJSON) UnmarshalJSON(data []byte) error { + *r = append((*r)[:0], data...) + return nil +} + +func (r rawJSON) MarshalJSON() ([]byte, error) { + if r == nil { + return []byte("null"), nil + } + return []byte(r), nil +} + // call makes a JSON-RPC 2.0 call to /json_rpc. func (c *Client) call(method string, params any, result any) error { - reqBody, err := json.Marshal(jsonRPCRequest{ + reqBody := core.JSONMarshalString(jsonRPCRequest{ JSONRPC: "2.0", ID: "0", Method: method, Params: params, }) - if err != nil { - return coreerr.E("Client.call", "marshal request", err) - } - resp, err := c.httpClient.Post(c.url, "application/json", bytes.NewReader(reqBody)) + resp, err := c.httpClient.Post(c.url, "application/json", bytes.NewReader([]byte(reqBody))) if err != nil { - return coreerr.E("Client.call", fmt.Sprintf("post %s", method), err) + return coreerr.E("Client.call", core.Sprintf("post %s", method), err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return coreerr.E("Client.call", fmt.Sprintf("http %d from %s", resp.StatusCode, method), nil) + return coreerr.E("Client.call", core.Sprintf("http %d from %s", resp.StatusCode, method), nil) } body, err := io.ReadAll(resp.Body) @@ -107,8 +117,8 @@ func (c *Client) call(method string, params any, result any) error { } var rpcResp jsonRPCResponse - if err := json.Unmarshal(body, &rpcResp); err != nil { - return coreerr.E("Client.call", "unmarshal response", err) + if r := core.JSONUnmarshalString(string(body), &rpcResp); !r.OK { + return coreerr.E("Client.call", "unmarshal response", r.Value.(error)) } if rpcResp.Error != nil { @@ -116,8 +126,8 @@ 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 coreerr.E("Client.call", "unmarshal result", err) + if r := core.JSONUnmarshalString(string(rpcResp.Result), result); !r.OK { + return coreerr.E("Client.call", "unmarshal result", r.Value.(error)) } } return nil @@ -125,20 +135,17 @@ func (c *Client) call(method string, params any, result any) error { // legacyCall makes a plain JSON POST to a legacy URI path (e.g. /getheight). func (c *Client) legacyCall(path string, params any, result any) error { - reqBody, err := json.Marshal(params) - if err != nil { - return coreerr.E("Client.legacyCall", "marshal request", err) - } + reqBody := core.JSONMarshalString(params) url := c.baseURL + path - resp, err := c.httpClient.Post(url, "application/json", bytes.NewReader(reqBody)) + resp, err := c.httpClient.Post(url, "application/json", bytes.NewReader([]byte(reqBody))) if err != nil { - return coreerr.E("Client.legacyCall", fmt.Sprintf("post %s", path), err) + return coreerr.E("Client.legacyCall", core.Sprintf("post %s", path), err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return coreerr.E("Client.legacyCall", fmt.Sprintf("http %d from %s", resp.StatusCode, path), nil) + return coreerr.E("Client.legacyCall", core.Sprintf("http %d from %s", resp.StatusCode, path), nil) } body, err := io.ReadAll(resp.Body) @@ -147,8 +154,8 @@ func (c *Client) legacyCall(path string, params any, result any) error { } if result != nil { - if err := json.Unmarshal(body, result); err != nil { - return coreerr.E("Client.legacyCall", "unmarshal response", err) + if r := core.JSONUnmarshalString(string(body), result); !r.OK { + return coreerr.E("Client.legacyCall", "unmarshal response", r.Value.(error)) } } return nil diff --git a/rpc/client_test.go b/rpc/client_test.go index 0bcad18..be841ae 100644 --- a/rpc/client_test.go +++ b/rpc/client_test.go @@ -36,8 +36,8 @@ func TestClient_Good_JSONRPCCall(t *testing.T) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(jsonRPCResponse{ JSONRPC: "2.0", - ID: json.RawMessage(`"0"`), - Result: json.RawMessage(`{"count":6300,"status":"OK"}`), + ID: rawJSON(`"0"`), + Result: rawJSON(`{"count":6300,"status":"OK"}`), }) })) defer srv.Close() @@ -88,7 +88,7 @@ func TestClient_Bad_RPCError(t *testing.T) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(jsonRPCResponse{ JSONRPC: "2.0", - ID: json.RawMessage(`"0"`), + ID: rawJSON(`"0"`), Error: &jsonRPCError{ Code: -2, Message: "TOO_BIG_HEIGHT", diff --git a/rpc/info.go b/rpc/info.go index 3ec5966..fa20590 100644 --- a/rpc/info.go +++ b/rpc/info.go @@ -6,8 +6,7 @@ package rpc import ( - "fmt" - + "dappco.re/go/core" coreerr "dappco.re/go/core/log" ) @@ -25,7 +24,7 @@ func (c *Client) GetInfo() (*DaemonInfo, error) { return nil, err } if resp.Status != "OK" { - return nil, coreerr.E("Client.GetInfo", fmt.Sprintf("getinfo: status %q", resp.Status), nil) + return nil, coreerr.E("Client.GetInfo", core.Sprintf("getinfo: status %q", resp.Status), nil) } return &resp.DaemonInfo, nil } @@ -41,7 +40,7 @@ func (c *Client) GetHeight() (uint64, error) { return 0, err } if resp.Status != "OK" { - return 0, coreerr.E("Client.GetHeight", fmt.Sprintf("getheight: status %q", resp.Status), nil) + return 0, coreerr.E("Client.GetHeight", core.Sprintf("getheight: status %q", resp.Status), nil) } return resp.Height, nil } @@ -56,7 +55,7 @@ func (c *Client) GetBlockCount() (uint64, error) { return 0, err } if resp.Status != "OK" { - return 0, coreerr.E("Client.GetBlockCount", fmt.Sprintf("getblockcount: status %q", resp.Status), nil) + return 0, coreerr.E("Client.GetBlockCount", core.Sprintf("getblockcount: status %q", resp.Status), nil) } return resp.Count, nil } diff --git a/rpc/info_test.go b/rpc/info_test.go index e04a560..4300dae 100644 --- a/rpc/info_test.go +++ b/rpc/info_test.go @@ -17,8 +17,8 @@ func TestGetInfo_Good(t *testing.T) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(jsonRPCResponse{ JSONRPC: "2.0", - ID: json.RawMessage(`"0"`), - Result: json.RawMessage(`{ + ID: rawJSON(`"0"`), + Result: rawJSON(`{ "height": 6300, "tx_count": 12345, "tx_pool_size": 3, @@ -83,8 +83,8 @@ func TestGetBlockCount_Good(t *testing.T) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(jsonRPCResponse{ JSONRPC: "2.0", - ID: json.RawMessage(`"0"`), - Result: json.RawMessage(`{"count":6301,"status":"OK"}`), + ID: rawJSON(`"0"`), + Result: rawJSON(`{"count":6301,"status":"OK"}`), }) })) defer srv.Close() diff --git a/rpc/mining.go b/rpc/mining.go index fcb3d7b..3601c5f 100644 --- a/rpc/mining.go +++ b/rpc/mining.go @@ -6,8 +6,7 @@ package rpc import ( - "fmt" - + "dappco.re/go/core" coreerr "dappco.re/go/core/log" ) @@ -24,7 +23,7 @@ func (c *Client) SubmitBlock(hexBlob string) error { return err } if resp.Status != "OK" { - return coreerr.E("Client.SubmitBlock", fmt.Sprintf("submitblock: status %q", resp.Status), nil) + return coreerr.E("Client.SubmitBlock", core.Sprintf("submitblock: status %q", resp.Status), nil) } return nil } @@ -51,7 +50,7 @@ func (c *Client) GetBlockTemplate(walletAddr string) (*BlockTemplateResponse, er return nil, err } if resp.Status != "OK" { - return nil, coreerr.E("Client.GetBlockTemplate", fmt.Sprintf("getblocktemplate: status %q", resp.Status), nil) + return nil, coreerr.E("Client.GetBlockTemplate", core.Sprintf("getblocktemplate: status %q", resp.Status), nil) } return &resp, nil } diff --git a/rpc/mining_test.go b/rpc/mining_test.go index 1996a45..3fa5708 100644 --- a/rpc/mining_test.go +++ b/rpc/mining_test.go @@ -31,8 +31,8 @@ func TestSubmitBlock_Good(t *testing.T) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(jsonRPCResponse{ JSONRPC: "2.0", - ID: json.RawMessage(`"0"`), - Result: json.RawMessage(`{"status":"OK"}`), + ID: rawJSON(`"0"`), + Result: rawJSON(`{"status":"OK"}`), }) })) defer srv.Close() @@ -49,7 +49,7 @@ func TestSubmitBlock_Bad_Rejected(t *testing.T) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(jsonRPCResponse{ JSONRPC: "2.0", - ID: json.RawMessage(`"0"`), + ID: rawJSON(`"0"`), Error: &jsonRPCError{Code: -7, Message: "BLOCK_NOT_ACCEPTED"}, }) })) @@ -86,8 +86,8 @@ func TestGetBlockTemplate_Good(t *testing.T) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(jsonRPCResponse{ JSONRPC: "2.0", - ID: json.RawMessage(`"0"`), - Result: json.RawMessage(`{ + ID: rawJSON(`"0"`), + Result: rawJSON(`{ "difficulty": "42", "height": 100, "blocktemplate_blob": "0100000000000000000000000000", @@ -122,8 +122,8 @@ func TestGetBlockTemplate_Bad_Status(t *testing.T) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(jsonRPCResponse{ JSONRPC: "2.0", - ID: json.RawMessage(`"0"`), - Result: json.RawMessage(`{"status":"BUSY"}`), + ID: rawJSON(`"0"`), + Result: rawJSON(`{"status":"BUSY"}`), }) })) defer srv.Close() diff --git a/rpc/transactions.go b/rpc/transactions.go index f691ace..4def8d3 100644 --- a/rpc/transactions.go +++ b/rpc/transactions.go @@ -6,8 +6,7 @@ package rpc import ( - "fmt" - + "dappco.re/go/core" coreerr "dappco.re/go/core/log" ) @@ -24,7 +23,7 @@ func (c *Client) GetTxDetails(txHash string) (*TxInfo, error) { return nil, err } if resp.Status != "OK" { - return nil, coreerr.E("Client.GetTxDetails", fmt.Sprintf("get_tx_details: status %q", resp.Status), nil) + return nil, coreerr.E("Client.GetTxDetails", core.Sprintf("get_tx_details: status %q", resp.Status), nil) } return &resp.TxInfo, nil } @@ -45,7 +44,7 @@ func (c *Client) GetTransactions(hashes []string) (txsHex []string, missed []str return nil, nil, err } if resp.Status != "OK" { - return nil, nil, coreerr.E("Client.GetTransactions", fmt.Sprintf("gettransactions: status %q", resp.Status), nil) + return nil, nil, coreerr.E("Client.GetTransactions", core.Sprintf("gettransactions: status %q", resp.Status), nil) } return resp.TxsAsHex, resp.MissedTx, nil } diff --git a/rpc/transactions_test.go b/rpc/transactions_test.go index ebc5488..09389ef 100644 --- a/rpc/transactions_test.go +++ b/rpc/transactions_test.go @@ -17,8 +17,8 @@ func TestGetTxDetails_Good(t *testing.T) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(jsonRPCResponse{ JSONRPC: "2.0", - ID: json.RawMessage(`"0"`), - Result: json.RawMessage(`{ + ID: rawJSON(`"0"`), + Result: rawJSON(`{ "status": "OK", "tx_info": { "id": "a6e8da986858e6825fce7a192097e6afae4e889cabe853a9c29b964985b23da8", @@ -55,7 +55,7 @@ func TestGetTxDetails_Bad_NotFound(t *testing.T) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(jsonRPCResponse{ JSONRPC: "2.0", - ID: json.RawMessage(`"0"`), + ID: rawJSON(`"0"`), Error: &jsonRPCError{Code: -14, Message: "NOT_FOUND"}, }) })) diff --git a/rpc/wallet.go b/rpc/wallet.go index 5d0b560..fa7e9fd 100644 --- a/rpc/wallet.go +++ b/rpc/wallet.go @@ -6,10 +6,9 @@ package rpc import ( - "encoding/hex" - "fmt" - + "dappco.re/go/core" coreerr "dappco.re/go/core/log" + "encoding/hex" ) // RandomOutputEntry is a decoy output returned by getrandom_outs. @@ -35,7 +34,7 @@ func (c *Client) GetRandomOutputs(amount uint64, count int) ([]RandomOutputEntry return nil, err } if resp.Status != "OK" { - return nil, coreerr.E("Client.GetRandomOutputs", fmt.Sprintf("getrandom_outs: status %q", resp.Status), nil) + return nil, coreerr.E("Client.GetRandomOutputs", core.Sprintf("getrandom_outs: status %q", resp.Status), nil) } return resp.Outs, nil } @@ -55,7 +54,7 @@ func (c *Client) SendRawTransaction(txBlob []byte) error { return err } if resp.Status != "OK" { - return coreerr.E("Client.SendRawTransaction", fmt.Sprintf("sendrawtransaction: status %q", resp.Status), nil) + return coreerr.E("Client.SendRawTransaction", core.Sprintf("sendrawtransaction: status %q", resp.Status), nil) } return nil } diff --git a/sync_service.go b/sync_service.go index da59e62..6102ddd 100644 --- a/sync_service.go +++ b/sync_service.go @@ -9,11 +9,11 @@ import ( "context" "crypto/rand" "encoding/binary" - "fmt" "log" "net" "time" + "dappco.re/go/core" coreerr "dappco.re/go/core/log" "dappco.re/go/core/blockchain/chain" @@ -56,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 coreerr.E("syncOnce", fmt.Sprintf("dial %s", seed), err) + return coreerr.E("syncOnce", core.Sprintf("dial %s", seed), err) } defer conn.Close() @@ -94,7 +94,7 @@ func syncOnce(ctx context.Context, c *chain.Chain, cfg *config.ChainConfig, opts return coreerr.E("syncOnce", "read handshake", err) } if hdr.Command != uint32(p2p.CommandHandshake) { - return coreerr.E("syncOnce", fmt.Sprintf("unexpected command %d", hdr.Command), nil) + return coreerr.E("syncOnce", core.Sprintf("unexpected command %d", hdr.Command), nil) } var resp p2p.HandshakeResponse diff --git a/tui/explorer_model.go b/tui/explorer_model.go index 2ac6b31..5d5ada0 100644 --- a/tui/explorer_model.go +++ b/tui/explorer_model.go @@ -6,10 +6,9 @@ package tui import ( - "fmt" - "strings" "time" + "dappco.re/go/core" cli "dappco.re/go/core/cli/pkg/cli" tea "github.com/charmbracelet/bubbletea" @@ -215,10 +214,10 @@ func (m *ExplorerModel) viewBlockList() string { return " no blocks \u2014 chain is empty" } - var b strings.Builder + b := core.NewBuilder() // Header row. - header := fmt.Sprintf(" %-8s %-18s %5s %12s %12s", + header := core.Sprintf(" %-8s %-18s %5s %12s %12s", "Height", "Hash", "Txs", "Difficulty", "Age") b.WriteString(header) b.WriteByte('\n') @@ -240,10 +239,10 @@ func (m *ExplorerModel) viewBlockList() string { prefix = "> " } - hashShort := fmt.Sprintf("%x", row.Hash[:4]) + "..." + hashShort := core.Concat(core.Sprintf("%x", row.Hash[:4]), "...") age := formatAge(time.Unix(int64(row.Timestamp), 0)) - line := fmt.Sprintf("%s%-8d %-18s %5d %12s %12s", + line := core.Sprintf("%s%-8d %-18s %5d %12s %12s", prefix, row.Height, hashShort, row.TxCount, formatDifficulty(row.Difficulty), age) @@ -264,17 +263,17 @@ func (m *ExplorerModel) viewBlockDetail() string { return " no block selected" } - var b strings.Builder + b := core.NewBuilder() meta := m.blockMeta blk := m.block - b.WriteString(fmt.Sprintf(" Block %d\n", meta.Height)) - b.WriteString(fmt.Sprintf(" Hash: %x\n", meta.Hash)) - b.WriteString(fmt.Sprintf(" Timestamp: %s\n", time.Unix(int64(meta.Timestamp), 0).UTC().Format(time.RFC3339))) - b.WriteString(fmt.Sprintf(" Difficulty: %s\n", formatDifficulty(meta.Difficulty))) - b.WriteString(fmt.Sprintf(" Version: %d.%d\n", blk.MajorVersion, blk.MinorVersion)) - b.WriteString(fmt.Sprintf(" Nonce: %d\n", blk.Nonce)) - b.WriteString(fmt.Sprintf(" Txs: %d\n\n", len(blk.TxHashes))) + b.WriteString(core.Sprintf(" Block %d\n", meta.Height)) + b.WriteString(core.Sprintf(" Hash: %x\n", meta.Hash)) + b.WriteString(core.Sprintf(" Timestamp: %s\n", time.Unix(int64(meta.Timestamp), 0).UTC().Format(time.RFC3339))) + b.WriteString(core.Sprintf(" Difficulty: %s\n", formatDifficulty(meta.Difficulty))) + b.WriteString(core.Sprintf(" Version: %d.%d\n", blk.MajorVersion, blk.MinorVersion)) + b.WriteString(core.Sprintf(" Nonce: %d\n", blk.Nonce)) + b.WriteString(core.Sprintf(" Txs: %d\n\n", len(blk.TxHashes))) if len(blk.TxHashes) == 0 { b.WriteString(" (coinbase only)") @@ -285,7 +284,7 @@ func (m *ExplorerModel) viewBlockDetail() string { if i == m.txCursor { prefix = "> " } - b.WriteString(fmt.Sprintf(" %s%x\n", prefix, txHash[:8])) + b.WriteString(core.Sprintf(" %s%x\n", prefix, txHash[:8])) } } @@ -297,25 +296,25 @@ func (m *ExplorerModel) viewTxDetail() string { return " no transaction selected" } - var b strings.Builder + b := core.NewBuilder() tx := m.tx b.WriteString(" Transaction\n") - b.WriteString(fmt.Sprintf(" Hash: %x\n", m.txHash)) - b.WriteString(fmt.Sprintf(" Version: %d\n", tx.Version)) - b.WriteString(fmt.Sprintf(" Inputs: %d\n", len(tx.Vin))) - b.WriteString(fmt.Sprintf(" Outputs: %d\n\n", len(tx.Vout))) + b.WriteString(core.Sprintf(" Hash: %x\n", m.txHash)) + b.WriteString(core.Sprintf(" Version: %d\n", tx.Version)) + b.WriteString(core.Sprintf(" Inputs: %d\n", len(tx.Vin))) + b.WriteString(core.Sprintf(" Outputs: %d\n\n", len(tx.Vout))) if len(tx.Vin) > 0 { b.WriteString(" Inputs:\n") for i, in := range tx.Vin { switch v := in.(type) { case types.TxInputGenesis: - b.WriteString(fmt.Sprintf(" [%d] coinbase height=%d\n", i, v.Height)) + b.WriteString(core.Sprintf(" [%d] coinbase height=%d\n", i, v.Height)) case types.TxInputToKey: - b.WriteString(fmt.Sprintf(" [%d] to_key amount=%d key_image=%x\n", i, v.Amount, v.KeyImage[:4])) + b.WriteString(core.Sprintf(" [%d] to_key amount=%d key_image=%x\n", i, v.Amount, v.KeyImage[:4])) default: - b.WriteString(fmt.Sprintf(" [%d] %T\n", i, v)) + b.WriteString(core.Sprintf(" [%d] %T\n", i, v)) } } } @@ -326,14 +325,14 @@ func (m *ExplorerModel) viewTxDetail() string { switch v := out.(type) { case types.TxOutputBare: if toKey, ok := v.Target.(types.TxOutToKey); ok { - b.WriteString(fmt.Sprintf(" [%d] bare amount=%d key=%x\n", i, v.Amount, toKey.Key[:4])) + b.WriteString(core.Sprintf(" [%d] bare amount=%d key=%x\n", i, v.Amount, toKey.Key[:4])) } else { - b.WriteString(fmt.Sprintf(" [%d] bare amount=%d target=%T\n", i, v.Amount, v.Target)) + b.WriteString(core.Sprintf(" [%d] bare amount=%d target=%T\n", i, v.Amount, v.Target)) } case types.TxOutputZarcanum: - b.WriteString(fmt.Sprintf(" [%d] zarcanum stealth=%x\n", i, v.StealthAddress[:4])) + b.WriteString(core.Sprintf(" [%d] zarcanum stealth=%x\n", i, v.StealthAddress[:4])) default: - b.WriteString(fmt.Sprintf(" [%d] %T\n", i, v)) + b.WriteString(core.Sprintf(" [%d] %T\n", i, v)) } } } diff --git a/tui/keyhints_model.go b/tui/keyhints_model.go index f4d459d..9c3d541 100644 --- a/tui/keyhints_model.go +++ b/tui/keyhints_model.go @@ -6,8 +6,7 @@ package tui import ( - "strings" - + "dappco.re/go/core" tea "github.com/charmbracelet/bubbletea" cli "dappco.re/go/core/cli/pkg/cli" @@ -44,7 +43,7 @@ func (m *KeyHintsModel) Update(msg tea.Msg) (cli.FrameModel, tea.Cmd) { // View renders a single-line hint bar separated by vertical bars. // The output is truncated to width if it would overflow. func (m *KeyHintsModel) View(width, height int) string { - line := " " + strings.Join(m.hints, " \u2502 ") + line := " " + core.Join(" \u2502 ", m.hints...) if len(line) > width && width > 0 { line = line[:width] } diff --git a/tui/status_model.go b/tui/status_model.go index ac6d405..ab79a5a 100644 --- a/tui/status_model.go +++ b/tui/status_model.go @@ -6,9 +6,9 @@ package tui import ( - "fmt" "time" + "dappco.re/go/core" tea "github.com/charmbracelet/bubbletea" cli "dappco.re/go/core/cli/pkg/cli" @@ -53,7 +53,7 @@ func (m *StatusModel) View(width, height int) string { line = " height 0 | syncing..." } else { s := m.status - line = fmt.Sprintf(" height %d | sync %.1f%% | diff %s | %d peers | tip %s", + line = core.Sprintf(" height %d | sync %.1f%% | diff %s | %d peers | tip %s", s.Height, s.SyncPct, formatDifficulty(s.Difficulty), s.PeerCount, formatAge(s.TipTime)) } if len(line) > width && width > 0 { @@ -70,13 +70,13 @@ func formatAge(t time.Time) string { d := time.Since(t) switch { case d < time.Minute: - return fmt.Sprintf("%ds ago", int(d.Seconds())) + return core.Sprintf("%ds ago", int(d.Seconds())) case d < time.Hour: - return fmt.Sprintf("%dm ago", int(d.Minutes())) + return core.Sprintf("%dm ago", int(d.Minutes())) case d < 24*time.Hour: - return fmt.Sprintf("%dh ago", int(d.Hours())) + return core.Sprintf("%dh ago", int(d.Hours())) default: - return fmt.Sprintf("%dd ago", int(d.Hours()/24)) + return core.Sprintf("%dd ago", int(d.Hours()/24)) } } @@ -84,12 +84,12 @@ func formatAge(t time.Time) string { func formatDifficulty(d uint64) string { switch { case d >= 1_000_000_000: - return fmt.Sprintf("%.1fG", float64(d)/1_000_000_000) + return core.Sprintf("%.1fG", float64(d)/1_000_000_000) case d >= 1_000_000: - return fmt.Sprintf("%.1fM", float64(d)/1_000_000) + return core.Sprintf("%.1fM", float64(d)/1_000_000) case d >= 1_000: - return fmt.Sprintf("%.1fK", float64(d)/1_000) + return core.Sprintf("%.1fK", float64(d)/1_000) default: - return fmt.Sprintf("%d", d) + return core.Sprintf("%d", d) } } diff --git a/types/address.go b/types/address.go index aa4feab..7cc5cf0 100644 --- a/types/address.go +++ b/types/address.go @@ -10,9 +10,9 @@ package types import ( - "fmt" "math/big" + "dappco.re/go/core" coreerr "dappco.re/go/core/log" "golang.org/x/crypto/sha3" @@ -99,7 +99,7 @@ func DecodeAddress(s string) (*Address, uint64, error) { // After the prefix we need exactly 32+32+1+4 = 69 bytes. remaining := raw[prefixLen:] if len(remaining) != 69 { - return nil, 0, coreerr.E("DecodeAddress", fmt.Sprintf("types: unexpected address data length: want 69 bytes after prefix, got %d", len(remaining)), nil) + return nil, 0, coreerr.E("DecodeAddress", core.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. @@ -223,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, coreerr.E("base58Decode", fmt.Sprintf("types: invalid base58 string length %d", len(s)), nil) + return nil, coreerr.E("base58Decode", core.Sprintf("types: invalid base58 string length %d", len(s)), nil) } var result []byte @@ -258,7 +258,7 @@ func decodeBlock(s string, byteCount int) ([]byte, error) { for _, c := range []byte(s) { idx := base58CharIndex(c) if idx < 0 { - return nil, coreerr.E("decodeBlock", fmt.Sprintf("types: invalid base58 character %q", c), nil) + return nil, coreerr.E("decodeBlock", core.Sprintf("types: invalid base58 character %q", c), nil) } num.Mul(num, base) num.Add(num, big.NewInt(int64(idx))) @@ -267,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, coreerr.E("decodeBlock", fmt.Sprintf("types: base58 block overflow: decoded %d bytes, expected %d", len(raw), byteCount), nil) + return nil, coreerr.E("decodeBlock", core.Sprintf("types: base58 block overflow: decoded %d bytes, expected %d", len(raw), byteCount), nil) } // Pad with leading zeroes if necessary. diff --git a/types/types.go b/types/types.go index 612403d..78800c4 100644 --- a/types/types.go +++ b/types/types.go @@ -13,10 +13,9 @@ package types import ( - "encoding/hex" - "fmt" - + "dappco.re/go/core" coreerr "dappco.re/go/core/log" + "encoding/hex" ) // Hash is a 256-bit (32-byte) hash value, typically produced by Keccak-256. @@ -45,7 +44,7 @@ func HashFromHex(s string) (Hash, error) { return h, coreerr.E("HashFromHex", "types: invalid hex for hash", err) } if len(b) != 32 { - return h, coreerr.E("HashFromHex", fmt.Sprintf("types: hash hex must be 64 characters, got %d", len(s)), nil) + return h, coreerr.E("HashFromHex", core.Sprintf("types: hash hex must be 64 characters, got %d", len(s)), nil) } copy(h[:], b) return h, nil @@ -70,7 +69,7 @@ func PublicKeyFromHex(s string) (PublicKey, error) { return pk, coreerr.E("PublicKeyFromHex", "types: invalid hex for public key", err) } if len(b) != 32 { - return pk, coreerr.E("PublicKeyFromHex", fmt.Sprintf("types: public key hex must be 64 characters, got %d", len(s)), nil) + return pk, coreerr.E("PublicKeyFromHex", core.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 0dbfdfd..181f97e 100644 --- a/wallet/account.go +++ b/wallet/account.go @@ -14,9 +14,9 @@ import ( "crypto/cipher" "crypto/rand" "encoding/hex" - "encoding/json" "io" + "dappco.re/go/core" coreerr "dappco.re/go/core/log" "golang.org/x/crypto/argon2" @@ -113,10 +113,7 @@ func (a *Account) Address() types.Address { // Save encrypts the account with Argon2id + AES-256-GCM and persists it to // the given store. The stored blob layout is: salt (16) | nonce (12) | ciphertext. func (a *Account) Save(s *store.Store, password string) error { - plaintext, err := json.Marshal(a) - if err != nil { - return coreerr.E("Account.Save", "wallet: marshal account", err) - } + plaintext := []byte(core.JSONMarshalString(a)) salt := make([]byte, saltLen) if _, err := io.ReadFull(rand.Reader, salt); err != nil { @@ -187,8 +184,8 @@ func LoadAccount(s *store.Store, password string) (*Account, error) { } var acc Account - if err := json.Unmarshal(plaintext, &acc); err != nil { - return nil, coreerr.E("LoadAccount", "wallet: unmarshal account", err) + if r := core.JSONUnmarshalString(string(plaintext), &acc); !r.OK { + return nil, coreerr.E("LoadAccount", "wallet: unmarshal account", r.Value.(error)) } return &acc, nil } diff --git a/wallet/builder.go b/wallet/builder.go index c8f3cb4..1150a5a 100644 --- a/wallet/builder.go +++ b/wallet/builder.go @@ -12,9 +12,9 @@ package wallet import ( "bytes" "cmp" - "fmt" "slices" + "dappco.re/go/core" coreerr "dappco.re/go/core/log" "dappco.re/go/core/blockchain/config" @@ -83,7 +83,7 @@ func (b *V1Builder) Build(req *BuildRequest) (*types.Transaction, error) { destTotal += dst.Amount } if sourceTotal < destTotal+req.Fee { - return nil, coreerr.E("V1Builder.Build", fmt.Sprintf("wallet: insufficient funds: have %d, need %d", sourceTotal, destTotal+req.Fee), nil) + return nil, coreerr.E("V1Builder.Build", core.Sprintf("wallet: insufficient funds: have %d, need %d", sourceTotal, destTotal+req.Fee), nil) } change := sourceTotal - destTotal - req.Fee @@ -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, coreerr.E("V1Builder.Build", fmt.Sprintf("wallet: input %d", i), buildErr) + return nil, coreerr.E("V1Builder.Build", core.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, coreerr.E("V1Builder.Build", fmt.Sprintf("wallet: output %d", outputIdx), outErr) + return nil, coreerr.E("V1Builder.Build", core.Sprintf("wallet: output %d", outputIdx), outErr) } tx.Vout = append(tx.Vout, out) outputIdx++ @@ -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, coreerr.E("V1Builder.Build", fmt.Sprintf("wallet: sign input %d", i), signErr) + return nil, coreerr.E("V1Builder.Build", core.Sprintf("wallet: sign input %d", i), signErr) } tx.Signatures = append(tx.Signatures, sigs) } diff --git a/wallet/extra.go b/wallet/extra.go index c2e35af..bd751ab 100644 --- a/wallet/extra.go +++ b/wallet/extra.go @@ -10,10 +10,9 @@ package wallet import ( - "encoding/binary" - "fmt" - + "dappco.re/go/core" coreerr "dappco.re/go/core/log" + "encoding/binary" "dappco.re/go/core/blockchain/types" "dappco.re/go/core/blockchain/wire" @@ -59,7 +58,7 @@ func ParseTxExtra(raw []byte) (*TxExtra, error) { switch tag { case extraTagPublicKey: if pos+32 > len(raw) { - return extra, coreerr.E("ParseTxExtra", fmt.Sprintf("wallet: extra: truncated public key at offset %d", pos), nil) + return extra, coreerr.E("ParseTxExtra", core.Sprintf("wallet: extra: truncated public key at offset %d", pos), nil) } copy(extra.TxPublicKey[:], raw[pos:pos+32]) pos += 32 @@ -112,11 +111,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, coreerr.E("skipExtraElement", fmt.Sprintf("wallet: extra: no data for string tag %d", tag), nil) + return 0, coreerr.E("skipExtraElement", core.Sprintf("wallet: extra: no data for string tag %d", tag), nil) } length, n, err := wire.DecodeVarint(data) if err != nil { - return 0, coreerr.E("skipExtraElement", fmt.Sprintf("wallet: extra: invalid string length for tag %d", tag), err) + return 0, coreerr.E("skipExtraElement", core.Sprintf("wallet: extra: invalid string length for tag %d", tag), err) } return n + int(length), nil @@ -124,7 +123,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, coreerr.E("skipExtraElement", fmt.Sprintf("wallet: extra: invalid varint for tag %d", tag), err) + return 0, coreerr.E("skipExtraElement", core.Sprintf("wallet: extra: invalid varint for tag %d", tag), err) } return n, nil @@ -141,6 +140,6 @@ func skipExtraElement(data []byte, tag uint8) (int, error) { return 64, nil // signature default: - return 0, coreerr.E("skipExtraElement", fmt.Sprintf("wallet: extra: unknown tag %d", tag), nil) + return 0, coreerr.E("skipExtraElement", core.Sprintf("wallet: extra: unknown tag %d", tag), nil) } } diff --git a/wallet/mnemonic.go b/wallet/mnemonic.go index 955c417..f58bff0 100644 --- a/wallet/mnemonic.go +++ b/wallet/mnemonic.go @@ -2,10 +2,9 @@ package wallet import ( "encoding/binary" - "fmt" "hash/crc32" - "strings" + "dappco.re/go/core" coreerr "dappco.re/go/core/log" ) @@ -14,7 +13,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 "", coreerr.E("MnemonicEncode", fmt.Sprintf("wallet: mnemonic encode requires 32 bytes, got %d", len(key)), nil) + return "", coreerr.E("MnemonicEncode", core.Sprintf("wallet: mnemonic encode requires 32 bytes, got %d", len(key)), nil) } words := make([]string, 0, 25) @@ -31,16 +30,16 @@ func MnemonicEncode(key []byte) (string, error) { checkIdx := checksumIndex(words) words = append(words, words[checkIdx]) - return strings.Join(words, " "), nil + return core.Join(" ", words...), nil } // MnemonicDecode converts a 25-word mnemonic phrase to a 32-byte secret key. func MnemonicDecode(phrase string) ([32]byte, error) { var key [32]byte - words := strings.Fields(phrase) + words := mnemonicWords(phrase) if len(words) != 25 { - return key, coreerr.E("MnemonicDecode", fmt.Sprintf("wallet: mnemonic requires 25 words, got %d", len(words)), nil) + return key, coreerr.E("MnemonicDecode", core.Sprintf("wallet: mnemonic requires 25 words, got %d", len(words)), nil) } expected := checksumIndex(words[:24]) @@ -62,7 +61,7 @@ func MnemonicDecode(phrase string) ([32]byte, error) { if !ok3 { word = words[i*3+2] } - return key, coreerr.E("MnemonicDecode", fmt.Sprintf("wallet: unknown mnemonic word %q", word), nil) + return key, coreerr.E("MnemonicDecode", core.Sprintf("wallet: unknown mnemonic word %q", word), nil) } val := uint32(w1) + @@ -74,6 +73,21 @@ func MnemonicDecode(phrase string) ([32]byte, error) { return key, nil } +func mnemonicWords(phrase string) []string { + normalised := core.Trim(phrase) + for _, ws := range []string{"\n", "\r", "\t"} { + normalised = core.Replace(normalised, ws, " ") + } + parts := core.Split(normalised, " ") + words := make([]string, 0, len(parts)) + for _, part := range parts { + if part != "" { + words = append(words, part) + } + } + return words +} + func checksumIndex(words []string) int { var prefixes string for _, w := range words { diff --git a/wallet/ring.go b/wallet/ring.go index 9cd3b16..5062ab8 100644 --- a/wallet/ring.go +++ b/wallet/ring.go @@ -10,8 +10,7 @@ package wallet import ( - "fmt" - + "dappco.re/go/core" coreerr "dappco.re/go/core/log" "dappco.re/go/core/blockchain/rpc" @@ -70,7 +69,7 @@ func (s *RPCRingSelector) SelectRing(amount uint64, realGlobalIndex uint64, ring } if len(members) < ringSize { - return nil, coreerr.E("RPCRingSelector.SelectRing", fmt.Sprintf("wallet: insufficient decoys: got %d, need %d", len(members), ringSize), nil) + return nil, coreerr.E("RPCRingSelector.SelectRing", core.Sprintf("wallet: insufficient decoys: got %d, need %d", len(members), ringSize), nil) } return members, nil } diff --git a/wallet/transfer.go b/wallet/transfer.go index 402d684..c45ead6 100644 --- a/wallet/transfer.go +++ b/wallet/transfer.go @@ -10,9 +10,7 @@ package wallet import ( - "encoding/json" - "fmt" - + "dappco.re/go/core" coreerr "dappco.re/go/core/log" store "dappco.re/go/core/store" @@ -66,22 +64,18 @@ func (t *Transfer) IsSpendable(chainHeight uint64, _ bool) bool { // putTransfer serialises a transfer as JSON and stores it in the given store, // keyed by the transfer's key image hex string. func putTransfer(s *store.Store, tr *Transfer) error { - val, err := json.Marshal(tr) - if err != nil { - return coreerr.E("putTransfer", "wallet: marshal transfer", err) - } - return s.Set(groupTransfers, tr.KeyImage.String(), string(val)) + return s.Set(groupTransfers, tr.KeyImage.String(), core.JSONMarshalString(tr)) } // getTransfer retrieves and deserialises a transfer by its key image. func getTransfer(s *store.Store, ki types.KeyImage) (*Transfer, error) { val, err := s.Get(groupTransfers, ki.String()) if err != nil { - return nil, coreerr.E("getTransfer", fmt.Sprintf("wallet: get transfer %s", ki), err) + return nil, coreerr.E("getTransfer", core.Sprintf("wallet: get transfer %s", ki), err) } var tr Transfer - if err := json.Unmarshal([]byte(val), &tr); err != nil { - return nil, coreerr.E("getTransfer", "wallet: unmarshal transfer", err) + if r := core.JSONUnmarshalString(val, &tr); !r.OK { + return nil, coreerr.E("getTransfer", "wallet: unmarshal transfer", r.Value.(error)) } return &tr, nil } @@ -107,7 +101,7 @@ func listTransfers(s *store.Store) ([]Transfer, error) { transfers := make([]Transfer, 0, len(pairs)) for _, val := range pairs { var tr Transfer - if err := json.Unmarshal([]byte(val), &tr); err != nil { + if r := core.JSONUnmarshalString(val, &tr); !r.OK { continue } transfers = append(transfers, tr) diff --git a/wallet/wallet.go b/wallet/wallet.go index f6be5cb..8138b89 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -11,10 +11,10 @@ package wallet import ( "cmp" - "fmt" "slices" "strconv" + "dappco.re/go/core" coreerr "dappco.re/go/core/log" "dappco.re/go/core/blockchain/chain" @@ -77,7 +77,7 @@ func (w *Wallet) Sync() error { for h := lastScanned; h < chainHeight; h++ { blk, _, err := w.chain.GetBlockByHeight(h) if err != nil { - return coreerr.E("Wallet.Sync", fmt.Sprintf("wallet: get block %d", h), err) + return coreerr.E("Wallet.Sync", core.Sprintf("wallet: get block %d", h), err) } // Scan miner tx. @@ -209,7 +209,7 @@ func (w *Wallet) Send(destinations []Destination, fee uint64) (*types.Transactio } } if selectedSum < needed { - return nil, coreerr.E("Wallet.Send", fmt.Sprintf("wallet: insufficient balance: have %d, need %d", selectedSum, needed), nil) + return nil, coreerr.E("Wallet.Send", core.Sprintf("wallet: insufficient balance: have %d, need %d", selectedSum, needed), nil) } req := &BuildRequest{ diff --git a/wire/decoder.go b/wire/decoder.go index 732932d..0239e13 100644 --- a/wire/decoder.go +++ b/wire/decoder.go @@ -7,9 +7,9 @@ package wire import ( "encoding/binary" - "fmt" "io" + "dappco.re/go/core" coreerr "dappco.re/go/core/log" ) @@ -82,7 +82,7 @@ func (d *Decoder) ReadBytes(n int) []byte { return nil } if n < 0 || n > MaxBlobSize { - d.err = coreerr.E("Decoder.ReadBytes", fmt.Sprintf("wire: blob size %d exceeds maximum %d", n, MaxBlobSize), nil) + d.err = coreerr.E("Decoder.ReadBytes", core.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 8a2720a..dc6736c 100644 --- a/wire/transaction.go +++ b/wire/transaction.go @@ -6,8 +6,7 @@ package wire import ( - "fmt" - + "dappco.re/go/core" coreerr "dappco.re/go/core/log" "dappco.re/go/core/blockchain/types" @@ -223,7 +222,7 @@ func decodeInputs(dec *Decoder) []types.TxInput { in.EtcDetails = decodeRawVariantVector(dec) vin = append(vin, in) default: - dec.err = coreerr.E("decodeInputs", fmt.Sprintf("wire: unsupported input tag 0x%02x", tag), nil) + dec.err = coreerr.E("decodeInputs", core.Sprintf("wire: unsupported input tag 0x%02x", tag), nil) return vin } } @@ -261,7 +260,7 @@ func decodeKeyOffsets(dec *Decoder) []types.TxOutRef { dec.ReadBlob32((*[32]byte)(&refs[i].TxID)) refs[i].N = dec.ReadVarint() default: - dec.err = coreerr.E("decodeKeyOffsets", fmt.Sprintf("wire: unsupported ref tag 0x%02x", refs[i].Tag), nil) + dec.err = coreerr.E("decodeKeyOffsets", core.Sprintf("wire: unsupported ref tag 0x%02x", refs[i].Tag), nil) return refs } } @@ -340,7 +339,7 @@ func decodeOutputsV1(dec *Decoder) []types.TxOutput { dec.ReadBlob32((*[32]byte)(&t.PKRefund)) out.Target = t default: - dec.err = coreerr.E("decodeOutputsV1", fmt.Sprintf("wire: unsupported target tag 0x%02x", tag), nil) + dec.err = coreerr.E("decodeOutputsV1", core.Sprintf("wire: unsupported target tag 0x%02x", tag), nil) return vout } vout = append(vout, out) @@ -427,7 +426,7 @@ func decodeOutputsV2(dec *Decoder) []types.TxOutput { dec.ReadBlob32((*[32]byte)(&t.PKRefund)) out.Target = t default: - dec.err = coreerr.E("decodeOutputsV2", fmt.Sprintf("wire: unsupported target tag 0x%02x", targetTag), nil) + dec.err = coreerr.E("decodeOutputsV2", core.Sprintf("wire: unsupported target tag 0x%02x", targetTag), nil) return vout } vout = append(vout, out) @@ -441,7 +440,7 @@ func decodeOutputsV2(dec *Decoder) []types.TxOutput { out.MixAttr = dec.ReadUint8() vout = append(vout, out) default: - dec.err = coreerr.E("decodeOutputsV2", fmt.Sprintf("wire: unsupported output tag 0x%02x", tag), nil) + dec.err = coreerr.E("decodeOutputsV2", core.Sprintf("wire: unsupported output tag 0x%02x", tag), nil) return vout } } @@ -627,7 +626,7 @@ func readVariantElementData(dec *Decoder, tag uint8) []byte { return dec.ReadBytes(96) default: - dec.err = coreerr.E("readVariantElementData", fmt.Sprintf("wire: unsupported variant tag 0x%02x (%d)", tag, tag), nil) + dec.err = coreerr.E("readVariantElementData", core.Sprintf("wire: unsupported variant tag 0x%02x (%d)", tag, tag), nil) return nil } } diff --git a/wire/varint.go b/wire/varint.go index b320a98..c708817 100644 --- a/wire/varint.go +++ b/wire/varint.go @@ -12,9 +12,7 @@ // to the C++ reference implementation. package wire -import ( - "errors" -) +import "dappco.re/go/core" // MaxVarintLen is the maximum number of bytes a CryptoNote varint can occupy. // A uint64 requires at most 10 bytes of 7-bit encoding (64 bits / 7 = ~9.14, @@ -23,11 +21,11 @@ const MaxVarintLen = 10 // ErrVarintOverflow is returned when a varint exceeds the maximum allowed // length of 10 bytes. -var ErrVarintOverflow = errors.New("wire: varint overflow (exceeds 10 bytes)") +var ErrVarintOverflow = core.E("", "wire: varint overflow (exceeds 10 bytes)", nil) // ErrVarintEmpty is returned when attempting to decode a varint from an // empty byte slice. -var ErrVarintEmpty = errors.New("wire: cannot decode varint from empty data") +var ErrVarintEmpty = core.E("", "wire: cannot decode varint from empty data", nil) // EncodeVarint encodes a uint64 value as a CryptoNote variable-length integer. //