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