cruzbit

package module
v0.0.0-...-50403b2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 6, 2023 License: MIT Imports: 49 Imported by: 0

README

A non-financial decentralized peer-to-peer ledger implementation

Much appreciation goes to the developers of Cruzbit for creating such an elegantly simple decentralized ledger. This project would not have been possible without Cruzbit, and as a tribute to their work, and because the project is all about citations, the genesis block of our ledger includes a message of appreciation that references the original Cruzbit developer's public key.

This implementation is based on Cruzbit, but with three key differences:

  • Transactions are now purely citations and no longer have a financial aspect to them- think hyperlinks between accounts.
  • The coinbase is issued from the public key: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
  • New transactions can only be initiated by recipients of the coinbase or recipients of a recipient of the coinbase.

The rest of this project will focus on exploring the implications of a de-financialized ledger and developing a possible ecosystem for it.

Background

In our societies, there is a lot of value that goes unnoticed but is still important, beyond what can be measured with money. Unfortunately, economic pressures lead us to focus too much on activities that only bring financial rewards. Despite the unhappiness and lack of fulfillment, many talented individuals still cling to jobs that offer salaries but lack purpose. This is a huge missed opportunity to solve the world's problems.

Serverse aims to capture and showcase all this untapped potential by creating a permanent and decentralized ledger of citations and recognition. There are already many humanitarian and free and open-source software communities that operate on similar informal models, using lists of sponsors or newsletters. Serverse could be a more formalized version of this recognition on a global scale.

At its core, Serverse can be a useful tool for free and open-source software projects and humanitarian efforts, for appreciation, donations, sponsorships, and other similar purposes. At its full potential, it could become a widely used web utility, where public keys act as portable "like" or "heart" buttons, usable in various contexts online or offline.

Getting started mining

  1. Install Go
  2. Install the wallet
  3. Run the wallet and issue a newkey command. Record the public key.
  4. Install the client
  5. Run the client using the public key from step 4. as the -pubkey argument.

See the original Cruzbit project this is based upon for more advanced options.

Next steps (removing barriers to entry)

  • Implement page rank on citations graph
  • Create block explorer + Pagerank visualisation
  • Praise/citation button browser extension
  • Praise QR-code graphic extension
  • Online wallet custodian service
  • Mobile wallets
  • Semantics documentation: Page rank, praise/citation

Documentation

Index

Constants

View Source
const (
	MAIN = iota
	SIDE
	ORPHAN
	UNKNOWN
)
View Source
const ArgonKeyLength = 32
View Source
const ArgonMemory = 64 * 1024
View Source
const ArgonSaltLength = 16
View Source
const ArgonThreads = 4
View Source
const ArgonTime = 1
View Source
const BITCOIN_CASH_RETARGET_ALGORITHM_HEIGHT = 28861

height at which we switch from bitcoin's difficulty adjustment algorithm to bitcoin cash's algorithm

View Source
const BLOCKS_UNTIL_NEW_SERIES = 1008 // 1 week in blocks
View Source
const BLOCKS_UNTIL_TRANSACTIONS_PER_BLOCK_DOUBLING = 105000 // 2 years in blocks
View Source
const COINBASE_MATURITY = 100 // blocks
View Source
const CUDA_ENABLED = false
View Source
const CheckpointsEnabled = false

CheckpointsEnabled can be disabled for testing.

View Source
const DEFAULT_CRUZBIT_PORT = 8832
View Source
const GenesisBlockJson = `` /* 766-byte string literal not displayed */

GenesisBlockJson is the first block in the chain. The memo field is the hash of the tip of the bitcoin blockchain at the time of this block's creation.

View Source
const INITIAL_MAX_TRANSACTIONS_PER_BLOCK = 10000 // 16.666... tx/sec, ~4 MBish in JSON
View Source
const INITIAL_TARGET = "00000000ffff0000000000000000000000000000000000000000000000000000"
View Source
const LatestCheckpointHeight = 0

LatestCheckpointHeight is used to determine if the client is synced.

View Source
const MAX_FUTURE_SECONDS = 2 * 60 * 60 // 2 hours
View Source
const MAX_INBOUND_PEER_CONNECTIONS = 128
View Source
const MAX_INBOUND_PEER_CONNECTIONS_FROM_SAME_HOST = 4
View Source
const MAX_MEMO_LENGTH = 100 // bytes (ascii/utf8 only)
View Source
const MAX_NUMBER int64 = 1<<53 - 1

given our JSON protocol we should respect Javascript's Number.MAX_SAFE_INTEGER value

View Source
const MAX_OUTBOUND_PEER_CONNECTIONS = 8
View Source
const MAX_PROTOCOL_MESSAGE_LENGTH = 2 * 1024 * 1024 // doesn't apply to blocks
View Source
const MAX_TIP_AGE = 24 * 60 * 60
View Source
const MAX_TRANSACTIONS_PER_BLOCK = 1<<31 - 1
View Source
const MAX_TRANSACTIONS_PER_BLOCK_EXCEEDED_AT_HEIGHT = 1852032 // pre-calculated
View Source
const MAX_TRANSACTIONS_TO_INCLUDE_PER_BLOCK = INITIAL_MAX_TRANSACTIONS_PER_BLOCK

if you change this it needs to be less than the maximum at the current height

View Source
const MAX_TRANSACTION_QUEUE_LENGTH = MAX_TRANSACTIONS_TO_INCLUDE_PER_BLOCK * 10
View Source
const NUM_BLOCKS_FOR_MEDIAN_TMESTAMP = 11
View Source
const OPENCL_ENABLED = false
View Source
const Protocol = "cruzbit.1"

Protocol is the name of this version of the cruzbit peer protocol.

View Source
const RETARGET_INTERVAL = 2016 // 2 weeks in blocks
View Source
const RETARGET_SMA_WINDOW = 144 // 1 day in blocks
View Source
const RETARGET_TIME = 1209600 // 2 weeks in seconds
View Source
const Server = "irc.freenode.net:7000"
View Source
const TARGET_SPACING = 600 // every 10 minutes

Variables

View Source
var Checkpoints map[int64]string = map[int64]string{}

Checkpoints are known height and block ID pairs on the main chain.

View Source
var PeerUpgrader = websocket.Upgrader{
	Subprotocols: []string{Protocol},
	CheckOrigin:  func(r *http.Request) bool { return true },
}

PeerUpgrader upgrades the incoming HTTP connection to a WebSocket if the subprotocol matches.

View Source
var Seeders = [...]string{
	"20.16.255.39:8832",
}

Functions

func CheckpointCheck

func CheckpointCheck(id BlockID, height int64) error

CheckpointCheck returns an error if the passed height is a checkpoint and the passed block ID does not match the given checkpoint block ID.

func CudaInit

func CudaInit() int

func CudaMinerMine

func CudaMinerMine(minerNum int, startNonce int64) int64

func CudaMinerUpdate

func CudaMinerUpdate(minerNum int, headerBytes []byte, headerBytesLen, startNonceOffset, endNonceOffset int, target BlockID) int64

func HandlePortForward

func HandlePortForward(port uint16, fwd bool) (string, bool, error)

HandlePortForward manages port (un)forwarding using UPnP.

func IsInitialBlockDownload

func IsInitialBlockDownload(ledger Ledger, blockStore BlockStorage) (bool, int64, error)

IsInitialBlockDownload returns true if it appears we're still syncing the block chain.

func OpenCLInit

func OpenCLInit() int

func OpenCLMinerMine

func OpenCLMinerMine(minerNum int, startNonce int64) int64

func OpenCLMinerUpdate

func OpenCLMinerUpdate(minerNum int, headerBytes []byte, headerBytesLen, startNonceOffset, endNonceOffset int, target BlockID) int64

Types

type BalanceCache

type BalanceCache struct {
	// contains filtered or unexported fields
}

BalanceCache maintains a partial unconfirmed view of the ledger. It's used by Ledger when (dis-)connecting blocks and by TransactionQueueMemory when deciding whether or not to add a transaction to the queue.

func NewBalanceCache

func NewBalanceCache(ledger Ledger) *BalanceCache

NewBalanceCache returns a new instance of a BalanceCache.

func (*BalanceCache) Apply

func (b *BalanceCache) Apply(tx *Transaction) (bool, error)

This applies the effect of the transaction to the invovled parties' cached passport (membership). It returns false if sender has received no existing appraisal by someone in the system.

func (*BalanceCache) Balances

func (b *BalanceCache) Balances() map[[ed25519.PublicKeySize]byte]int64

Balances returns the underlying cache of balances.

func (*BalanceCache) Reset

func (b *BalanceCache) Reset()

Reset resets the balance cache.

func (*BalanceCache) Undo

func (b *BalanceCache) Undo(tx *Transaction) error

Undo undoes the effects of a transaction on the invovled parties' cached balances.

type BalanceMessage

type BalanceMessage struct {
	BlockID   *BlockID          `json:"block_id,omitempty"`
	Height    int64             `json:"height,omitempty"`
	PublicKey ed25519.PublicKey `json:"public_key"`
	Balance   int64             `json:"balance"`
	Error     string            `json:"error,omitempty"`
}

BalanceMessage is used to send a public key's balance to a peer. Type: "balance".

type BalancesMessage

type BalancesMessage struct {
	BlockID  *BlockID           `json:"block_id,omitempty"`
	Height   int64              `json:"height,omitempty"`
	Balances []PublicKeyBalance `json:"balances,omitempty"`
	Error    string             `json:"error,omitempty"`
}

BalancesMessage is used to send a public key balances to a peer. Type: "balances".

type Block

type Block struct {
	Header       *BlockHeader   `json:"header"`
	Transactions []*Transaction `json:"transactions"`
	// contains filtered or unexported fields
}

Block represents a block in the block chain. It has a header and a list of transactions. As blocks are connected their transactions affect the underlying ledger.

func NewBlock

func NewBlock(previous BlockID, height int64, target, chainWork BlockID, transactions []*Transaction) (
	*Block, error)

NewBlock creates and returns a new Block to be mined.

func (*Block) AddTransaction

func (b *Block) AddTransaction(id TransactionID, tx *Transaction) error

AddTransaction adds a new transaction to the block. Called by miner when mining a new block.

func (Block) CheckPOW

func (b Block) CheckPOW(id BlockID) bool

CheckPOW verifies the block's proof-of-work satisfies the declared target.

func (Block) ID

func (b Block) ID() (BlockID, error)

ID computes an ID for a given block.

type BlockHeader

type BlockHeader struct {
	Previous         BlockID       `json:"previous"`
	HashListRoot     TransactionID `json:"hash_list_root"`
	Time             int64         `json:"time"`
	Target           BlockID       `json:"target"`
	ChainWork        BlockID       `json:"chain_work"` // total cumulative chain work
	Nonce            int64         `json:"nonce"`      // not used for crypto
	Height           int64         `json:"height"`
	TransactionCount int32         `json:"transaction_count"`
	// contains filtered or unexported fields
}

BlockHeader contains data used to determine block validity and its place in the block chain.

func (BlockHeader) Compare

func (header BlockHeader) Compare(theirHeader *BlockHeader, thisWhen, theirWhen int64) bool

Compare returns true if the header indicates it is a better chain than "theirHeader" up to both points. "thisWhen" is the timestamp of when we stored this block header. "theirWhen" is the timestamp of when we stored "theirHeader".

func (BlockHeader) ID

func (header BlockHeader) ID() (BlockID, error)

ID computes an ID for a given block header.

func (*BlockHeader) IDFast

func (header *BlockHeader) IDFast(minerNum int) (*big.Int, int64)

IDFast computes an ID for a given block header when mining.

type BlockHeaderHasher

type BlockHeaderHasher struct {
	// contains filtered or unexported fields
}

BlockHeaderHasher is used to more efficiently hash JSON serialized block headers while mining.

func NewBlockHeaderHasher

func NewBlockHeaderHasher() *BlockHeaderHasher

NewBlockHeaderHasher returns a newly initialized BlockHeaderHasher

func (*BlockHeaderHasher) Update

func (h *BlockHeaderHasher) Update(minerNum int, header *BlockHeader) (*big.Int, int64)

Update is called everytime the header is updated and the caller wants its new hash value/ID.

type BlockHeaderMessage

type BlockHeaderMessage struct {
	BlockID     *BlockID     `json:"block_id,omitempty"`
	BlockHeader *BlockHeader `json:"header,omitempty"`
}

BlockHeaderMessage is used to send a peer a block's header. Type: "block_header".

type BlockID

type BlockID [32]byte // SHA3-256 hash

BlockID is a block's unique identifier.

func (BlockID) GetBigInt

func (id BlockID) GetBigInt() *big.Int

GetBigInt converts from BlockID to big.Int.

func (BlockID) MarshalJSON

func (id BlockID) MarshalJSON() ([]byte, error)

MarshalJSON marshals BlockID as a hex string.

func (*BlockID) SetBigInt

func (id *BlockID) SetBigInt(i *big.Int) *BlockID

SetBigInt converts from big.Int to BlockID.

func (BlockID) String

func (id BlockID) String() string

String implements the Stringer interface

func (*BlockID) UnmarshalJSON

func (id *BlockID) UnmarshalJSON(b []byte) error

UnmarshalJSON unmarshals BlockID hex string to BlockID.

type BlockMessage

type BlockMessage struct {
	BlockID *BlockID `json:"block_id,omitempty"`
	Block   *Block   `json:"block,omitempty"`
}

BlockMessage is used to send a peer a complete block. Type: "block".

type BlockQueue

type BlockQueue struct {
	// contains filtered or unexported fields
}

BlockQueue is a queue of blocks to download.

func NewBlockQueue

func NewBlockQueue() *BlockQueue

NewBlockQueue returns a new instance of a BlockQueue.

func (*BlockQueue) Add

func (b *BlockQueue) Add(id BlockID, who string) bool

Add adds the block ID to the back of the queue and records the address of the peer who pushed it if it didn't exist in the queue. If it did exist and maxQueueWait has elapsed, the block is left in its position but the peer responsible for download is updated.

func (*BlockQueue) Exists

func (b *BlockQueue) Exists(id BlockID) bool

Exists returns true if the block ID exists in the queue.

func (*BlockQueue) Len

func (b *BlockQueue) Len() int

Len returns the length of the queue.

func (*BlockQueue) Peek

func (b *BlockQueue) Peek() (BlockID, bool)

Peek returns the ID of the block at the front of the queue.

func (*BlockQueue) Remove

func (b *BlockQueue) Remove(id BlockID, who string) bool

Remove removes the block ID from the queue only if the requester is who is currently responsible for its download.

type BlockStorage

type BlockStorage interface {
	// Store is called to store all of the block's information.
	Store(id BlockID, block *Block, now int64) error

	// Get returns the referenced block.
	GetBlock(id BlockID) (*Block, error)

	// GetBlockBytes returns the referenced block as a byte slice.
	GetBlockBytes(id BlockID) ([]byte, error)

	// GetBlockHeader returns the referenced block's header and the timestamp of when it was stored.
	GetBlockHeader(id BlockID) (*BlockHeader, int64, error)

	// GetTransaction returns a transaction within a block and the block's header.
	GetTransaction(id BlockID, index int) (*Transaction, *BlockHeader, error)
}

BlockStorage is an interface for storing blocks and their transactions.

type BlockStorageDisk

type BlockStorageDisk struct {
	// contains filtered or unexported fields
}

BlockStorageDisk is an on-disk BlockStorage implementation using the filesystem for blocks and LevelDB for block headers.

func NewBlockStorageDisk

func NewBlockStorageDisk(dirPath, dbPath string, readOnly, compress bool) (*BlockStorageDisk, error)

NewBlockStorageDisk returns a new instance of on-disk block storage.

func (*BlockStorageDisk) Close

func (b *BlockStorageDisk) Close() error

Close is called to close any underlying storage.

func (BlockStorageDisk) GetBlock

func (b BlockStorageDisk) GetBlock(id BlockID) (*Block, error)

Get returns the referenced block.

func (BlockStorageDisk) GetBlockBytes

func (b BlockStorageDisk) GetBlockBytes(id BlockID) ([]byte, error)

GetBlockBytes returns the referenced block as a byte slice.

func (BlockStorageDisk) GetBlockHeader

func (b BlockStorageDisk) GetBlockHeader(id BlockID) (*BlockHeader, int64, error)

GetBlockHeader returns the referenced block's header and the timestamp of when it was stored.

func (BlockStorageDisk) GetTransaction

func (b BlockStorageDisk) GetTransaction(id BlockID, index int) (
	*Transaction, *BlockHeader, error)

GetTransaction returns a transaction within a block and the block's header.

func (BlockStorageDisk) Store

func (b BlockStorageDisk) Store(id BlockID, block *Block, now int64) error

Store is called to store all of the block's information.

type BranchType

type BranchType int

BranchType indicates the type of branch a particular block resides on. Only blocks currently on the main branch are considered confirmed and only transactions in those blocks affect public key balances. Values are: MAIN, SIDE, ORPHAN or UNKNOWN.

type DNSSeeder

type DNSSeeder struct {
	// contains filtered or unexported fields
}

DNSSeeder returns known peers in response to DNS queries.

func NewDNSSeeder

func NewDNSSeeder(peerStore PeerStorage, port int) *DNSSeeder

NewDNSSeeder creates a new DNS seeder given a PeerStorage interface.

func (*DNSSeeder) Run

func (d *DNSSeeder) Run()

Run executes the main loop for the DNSSeeder in its own goroutine.

func (*DNSSeeder) Shutdown

func (d *DNSSeeder) Shutdown()

Shutdown shuts down the DNS seeder server.

type FilterAddMessage

type FilterAddMessage struct {
	PublicKeys []ed25519.PublicKey `json:"public_keys"`
}

FilterAddMessage is used to request the addition of the given public keys to the current filter. The filter is created if it's not set. Type: "filter_add".

type FilterBlockMessage

type FilterBlockMessage struct {
	BlockID      BlockID        `json:"block_id"`
	Header       *BlockHeader   `json:"header"`
	Transactions []*Transaction `json:"transactions"`
}

FilterBlockMessage represents a pared down block containing only transactions relevant to the peer given their filter. Type: "filter_block".

type FilterLoadMessage

type FilterLoadMessage struct {
	Type   string `json:"type"`
	Filter []byte `json:"filter"`
}

FilterLoadMessage is used to request that we load a filter which is used to filter transactions returned to the peer based on interest. Type: "filter_load"

type FilterResultMessage

type FilterResultMessage struct {
	Error string `json:"error,omitempty"`
}

FilterResultMessage indicates whether or not the filter request was successful. Type: "filter_result".

type FilterTransactionQueueMessage

type FilterTransactionQueueMessage struct {
	Transactions []*Transaction `json:"transactions"`
	Error        string         `json:"error,omitempty"`
}

FilterTransactionQueueMessage returns a pared down view of the unconfirmed transaction queue containing only transactions relevant to the peer given their filter. Type: "filter_transaction_queue".

type FindCommonAncestorMessage

type FindCommonAncestorMessage struct {
	BlockIDs []BlockID `json:"block_ids"`
}

FindCommonAncestorMessage is used to find a common ancestor with a peer. Type: "find_common_ancestor".

type GetBalanceMessage

type GetBalanceMessage struct {
	PublicKey ed25519.PublicKey `json:"public_key"`
}

GetBalanceMessage requests a public key's balance. Type: "get_balance".

type GetBalancesMessage

type GetBalancesMessage struct {
	PublicKeys []ed25519.PublicKey `json:"public_keys"`
}

GetBalancesMessage requests a set of public key balances. Type: "get_balances".

type GetBlockByHeightMessage

type GetBlockByHeightMessage struct {
	Height int64 `json:"height"`
}

GetBlockByHeightMessage is used to request a block for download. Type: "get_block_by_height".

type GetBlockHeaderByHeightMessage

type GetBlockHeaderByHeightMessage struct {
	Height int64 `json:"height"`
}

GetBlockHeaderByHeightMessage is used to request a block header. Type: "get_block_header_by_height".

type GetBlockHeaderMessage

type GetBlockHeaderMessage struct {
	BlockID BlockID `json:"block_id"`
}

GetBlockHeaderMessage is used to request a block header. Type: "get_block_header".

type GetBlockMessage

type GetBlockMessage struct {
	BlockID BlockID `json:"block_id"`
}

GetBlockMessage is used to request a block for download. Type: "get_block".

type GetPublicKeyTransactionsMessage

type GetPublicKeyTransactionsMessage struct {
	PublicKey   ed25519.PublicKey `json:"public_key"`
	StartHeight int64             `json:"start_height"`
	StartIndex  int               `json:"start_index"`
	EndHeight   int64             `json:"end_height"`
	Limit       int               `json:"limit"`
}

GetPublicKeyTransactionsMessage requests transactions associated with a given public key over a given height range of the block chain. Type: "get_public_key_transactions".

type GetTransactionMessage

type GetTransactionMessage struct {
	TransactionID TransactionID `json:"transaction_id"`
}

GetTransactionMessage is used to request a confirmed transaction. Type: "get_transaction".

type GetWorkMessage

type GetWorkMessage struct {
	PublicKeys []ed25519.PublicKey `json:"public_keys"`
	Memo       string              `json:"memo,omitempty"`
}

GetWorkMessage is used by a mining peer to request mining work. Type: "get_work"

type HashWithRead

type HashWithRead interface {
	hash.Hash

	// the sha3 state objects aren't exported from stdlib but some of their methods like Read are.
	// we can get the sum without the clone done by Sum which saves us a malloc in the fast path
	Read(out []byte) (n int, err error)
}

HashWithRead extends hash.Hash to provide a Read interface.

type HashrateMonitor

type HashrateMonitor struct {
	// contains filtered or unexported fields
}

HashrateMonitor collects hash counts from all miners in order to monitor and display the aggregate hashrate.

func NewHashrateMonitor

func NewHashrateMonitor(hashUpdateChan chan int64) *HashrateMonitor

NewHashrateMonitor returns a new HashrateMonitor instance.

func (*HashrateMonitor) Run

func (h *HashrateMonitor) Run()

Run executes the hashrate monitor's main loop in its own goroutine.

func (*HashrateMonitor) Shutdown

func (h *HashrateMonitor) Shutdown()

Shutdown stops the hashrate monitor synchronously.

type IRC

type IRC struct {
	// contains filtered or unexported fields
}

IRC is used for bootstrapping the network for now. It primarily exists as a backup to our current limited set of DNS seeders.

func NewIRC

func NewIRC() *IRC

NewIRC returns a new IRC bootstrapper.

func (*IRC) Connect

func (i *IRC) Connect(genesisID BlockID, port int, addrChan chan<- string) error

Connect connects the IRC bootstrapper to the IRC network. port is our local cruzbit port. If it's set to 0 we won't be used for inbound connections.

func (*IRC) Run

func (i *IRC) Run()

Run executes the IRC bootstrapper's main loop in its own goroutine.

func (*IRC) Shutdown

func (i *IRC) Shutdown()

Shutdown stops the IRC bootstrapper.

type InvBlockMessage

type InvBlockMessage struct {
	BlockIDs []BlockID `json:"block_ids"`
}

InvBlockMessage is used to communicate blocks available for download. Type: "inv_block".

type Ledger

type Ledger interface {
	// GetChainTip returns the ID and the height of the block at the current tip of the main chain.
	GetChainTip() (*BlockID, int64, error)

	// GetBlockIDForHeight returns the ID of the block at the given block chain height.
	GetBlockIDForHeight(height int64) (*BlockID, error)

	// SetBranchType sets the branch type for the given block.
	SetBranchType(id BlockID, branchType BranchType) error

	// GetBranchType returns the branch type for the given block.
	GetBranchType(id BlockID) (BranchType, error)

	// ConnectBlock connects a block to the tip of the block chain and applies the transactions
	// to the ledger.
	ConnectBlock(id BlockID, block *Block) ([]TransactionID, error)

	// DisconnectBlock disconnects a block from the tip of the block chain and undoes the effects
	// of the transactions on the ledger.
	DisconnectBlock(id BlockID, block *Block) ([]TransactionID, error)

	// GetPublicKeyBalance returns the current balance of a given public key.
	GetPublicKeyBalance(pubKey ed25519.PublicKey) (int64, error)

	// GetPublicKeyBalances returns the current balance of the given public keys
	// along with block ID and height of the corresponding main chain tip.
	GetPublicKeyBalances(pubKeys []ed25519.PublicKey) (
		map[[ed25519.PublicKeySize]byte]int64, *BlockID, int64, error)

	// GetTransactionIndex returns the index of a processed transaction.
	GetTransactionIndex(id TransactionID) (*BlockID, int, error)

	// GetPublicKeyTransactionIndicesRange returns transaction indices involving a given public key
	// over a range of heights. If startHeight > endHeight this iterates in reverse.
	GetPublicKeyTransactionIndicesRange(
		pubKey ed25519.PublicKey, startHeight, endHeight int64, startIndex, limit int) (
		[]BlockID, []int, int64, int, error)

	// Balance returns the total current ledger balance by summing the balance of all public keys.
	// It's only used offline for verification purposes.
	Balance() (int64, error)

	// GetPublicKeyBalanceAt returns the public key balance at the given height.
	// It's only used offline for historical and verification purposes.
	// This is only accurate when the full block chain is indexed (pruning disabled.)
	GetPublicKeyBalanceAt(pubKey ed25519.PublicKey, height int64) (int64, error)
}

Ledger is an interface to a ledger built from the most-work chain of blocks. It manages and computes public key balances as well as transaction and public key transaction indices. It also maintains an index of the block chain by height as well as branch information.

type LedgerDisk

type LedgerDisk struct {
	// contains filtered or unexported fields
}

LedgerDisk is an on-disk implemenation of the Ledger interface using LevelDB.

func NewLedgerDisk

func NewLedgerDisk(dbPath string, readOnly, prune bool, blockStore BlockStorage) (*LedgerDisk, error)

NewLedgerDisk returns a new instance of LedgerDisk.

func (LedgerDisk) Balance

func (l LedgerDisk) Balance() (int64, error)

Balance returns the total current ledger balance by summing the balance of all public keys. It's only used offline for verification purposes.

func (LedgerDisk) Close

func (l LedgerDisk) Close() error

Close is called to close any underlying storage.

func (LedgerDisk) ConnectBlock

func (l LedgerDisk) ConnectBlock(id BlockID, block *Block) ([]TransactionID, error)

ConnectBlock connects a block to the tip of the block chain and applies the transactions to the ledger.

func (LedgerDisk) DisconnectBlock

func (l LedgerDisk) DisconnectBlock(id BlockID, block *Block) ([]TransactionID, error)

DisconnectBlock disconnects a block from the tip of the block chain and undoes the effects of the transactions on the ledger.

func (LedgerDisk) GetBlockIDForHeight

func (l LedgerDisk) GetBlockIDForHeight(height int64) (*BlockID, error)

GetBlockIDForHeight returns the ID of the block at the given block chain height.

func (LedgerDisk) GetBranchType

func (l LedgerDisk) GetBranchType(id BlockID) (BranchType, error)

GetBranchType returns the branch type for the given block.

func (LedgerDisk) GetChainTip

func (l LedgerDisk) GetChainTip() (*BlockID, int64, error)

GetChainTip returns the ID and the height of the block at the current tip of the main chain.

func (LedgerDisk) GetPublicKeyBalance

func (l LedgerDisk) GetPublicKeyBalance(pubKey ed25519.PublicKey) (int64, error)

GetPublicKeyBalance returns the current balance of a given public key.

func (LedgerDisk) GetPublicKeyBalanceAt

func (l LedgerDisk) GetPublicKeyBalanceAt(pubKey ed25519.PublicKey, height int64) (int64, error)

GetPublicKeyBalanceAt returns the public key balance at the given height. It's only used offline for historical and verification purposes. This is only accurate when the full block chain is indexed (pruning disabled.)

func (LedgerDisk) GetPublicKeyBalances

func (l LedgerDisk) GetPublicKeyBalances(pubKeys []ed25519.PublicKey) (
	map[[ed25519.PublicKeySize]byte]int64, *BlockID, int64, error)

GetPublicKeyBalances returns the current balance of the given public keys along with block ID and height of the corresponding main chain tip.

func (LedgerDisk) GetPublicKeyTransactionIndicesRange

func (l LedgerDisk) GetPublicKeyTransactionIndicesRange(
	pubKey ed25519.PublicKey, startHeight, endHeight int64, startIndex, limit int) (
	[]BlockID, []int, int64, int, error)

GetPublicKeyTransactionIndicesRange returns transaction indices involving a given public key over a range of heights. If startHeight > endHeight this iterates in reverse.

func (LedgerDisk) GetTransactionIndex

func (l LedgerDisk) GetTransactionIndex(id TransactionID) (*BlockID, int, error)

GetTransactionIndex returns the index of a processed transaction.

func (LedgerDisk) SetBranchType

func (l LedgerDisk) SetBranchType(id BlockID, branchType BranchType) error

SetBranchType sets the branch type for the given block.

type Message

type Message struct {
	Type string      `json:"type"`
	Body interface{} `json:"body,omitempty"`
}

Message is a message frame for all messages in the cruzbit.1 protocol.

type Miner

type Miner struct {
	// contains filtered or unexported fields
}

Miner tries to mine a new tip block.

func NewMiner

func NewMiner(pubKeys []ed25519.PublicKey, memo string,
	blockStore BlockStorage, txQueue TransactionQueue,
	ledger Ledger, processor *Processor,
	hashUpdateChan chan int64, num int) *Miner

NewMiner returns a new Miner instance.

func (*Miner) Run

func (m *Miner) Run()

Run executes the miner's main loop in its own goroutine.

func (*Miner) Shutdown

func (m *Miner) Shutdown()

Shutdown stops the miner synchronously.

type NewTx

type NewTx struct {
	TransactionID TransactionID // transaction ID
	Transaction   *Transaction  // new transaction
	Source        string        // who sent it
}

NewTx is a message sent to registered new transaction channels when a transaction is queued.

type Peer

type Peer struct {
	// contains filtered or unexported fields
}

Peer is a peer client in the network. They all speak WebSocket protocol to each other. Peers could be fully validating and mining nodes or simply wallets.

func NewPeer

func NewPeer(conn *websocket.Conn, genesisID BlockID, peerStore PeerStorage,
	blockStore BlockStorage, ledger Ledger, processor *Processor,
	txQueue TransactionQueue, blockQueue *BlockQueue, addrChan chan<- string) *Peer

NewPeer returns a new instance of a peer.

func (*Peer) Connect

func (p *Peer) Connect(ctx context.Context, addr, nonce, myAddr string) (int, error)

Connect connects outbound to a peer.

func (*Peer) OnClose

func (p *Peer) OnClose(closeHandler func())

OnClose specifies a handler to call when the peer connection is closed.

func (*Peer) Run

func (p *Peer) Run()

Run executes the peer's main loop in its own goroutine. It manages reading and writing to the peer's WebSocket and facilitating the protocol.

func (*Peer) Shutdown

func (p *Peer) Shutdown()

Shutdown is called to shutdown the underlying WebSocket synchronously.

type PeerAddressesMessage

type PeerAddressesMessage struct {
	Addresses []string `json:"addresses"`
}

PeerAddressesMessage is used to communicate a list of potential peer addresses known by a peer. Type: "peer_addresses". Sent in response to the empty "get_peer_addresses" message type.

type PeerManager

type PeerManager struct {
	// contains filtered or unexported fields
}

PeerManager manages incoming and outgoing peer connections on behalf of the client. It also manages finding peers to connect to.

func NewPeerManager

func NewPeerManager(
	genesisID BlockID, peerStore PeerStorage, blockStore BlockStorage,
	ledger Ledger, processor *Processor, txQueue TransactionQueue,
	dataDir, myExternalIP, peer, certPath, keyPath string,
	port, inboundLimit int, accept, irc, dnsseed bool, banMap map[string]bool) *PeerManager

NewPeerManager returns a new PeerManager instance.

func (*PeerManager) Run

func (p *PeerManager) Run()

Run executes the PeerManager's main loop in its own goroutine. It determines our connectivity and manages sourcing peer addresses from seed sources as well as maintaining full outbound connections and accepting inbound connections.

func (*PeerManager) Shutdown

func (p *PeerManager) Shutdown()

Shutdown stops the peer manager synchronously.

type PeerStorage

type PeerStorage interface {
	// Store stores a peer address. Returns true if the peer was newly added to storage.
	Store(addr string) (bool, error)

	// Get returns some peers for us to attempt to connect to.
	Get(count int) ([]string, error)

	// GetSince returns some peers to tell others about last active less than "when" ago.
	GetSince(count int, when int64) ([]string, error)

	// Delete is called to explicitly remove a peer address from storage.
	Delete(addr string) error

	// OnConnectAttempt is called prior to attempting to connect to the peer.
	OnConnectAttempt(addr string) error

	// OnConnectSuccess is called upon successful handshake with the peer.
	OnConnectSuccess(addr string) error

	// OnConnectFailure is called upon connection failure.
	OnConnectFailure(addr string) error

	// OnDisconnect is called upon disconnection.
	OnDisconnect(addr string) error
}

PeerStorage is an interface for storing peer addresses and information about their connectivity.

type PeerStorageDisk

type PeerStorageDisk struct {
	// contains filtered or unexported fields
}

PeerStorageDisk is an on-disk implementation of the PeerStorage interface using LevelDB.

func NewPeerStorageDisk

func NewPeerStorageDisk(dbPath string) (*PeerStorageDisk, error)

NewPeerStorageDisk returns a new PeerStorageDisk instance.

func (*PeerStorageDisk) Close

func (p *PeerStorageDisk) Close() error

Close is called to close any underlying storage.

func (*PeerStorageDisk) Delete

func (p *PeerStorageDisk) Delete(addr string) error

Delete is called to explicitly remove a peer address from storage.

func (*PeerStorageDisk) Get

func (p *PeerStorageDisk) Get(count int) ([]string, error)

Get returns some peers for us to attempt to connect to.

func (*PeerStorageDisk) GetSince

func (p *PeerStorageDisk) GetSince(count int, when int64) ([]string, error)

GetSince returns some peers to tell others about last active less than "when" ago.

func (*PeerStorageDisk) OnConnectAttempt

func (p *PeerStorageDisk) OnConnectAttempt(addr string) error

OnConnectAttempt is called prior to attempting to connect to the peer.

func (*PeerStorageDisk) OnConnectFailure

func (p *PeerStorageDisk) OnConnectFailure(addr string) error

OnConnectFailure is called upon connection failure.

func (*PeerStorageDisk) OnConnectSuccess

func (p *PeerStorageDisk) OnConnectSuccess(addr string) error

OnConnectSuccess is called upon successful handshake with the peer.

func (*PeerStorageDisk) OnDisconnect

func (p *PeerStorageDisk) OnDisconnect(addr string) error

OnDisconnect is called upon disconnection.

func (*PeerStorageDisk) Store

func (p *PeerStorageDisk) Store(addr string) (bool, error)

Store stores a peer address. Returns true if the peer was newly added to storage.

type Processor

type Processor struct {
	// contains filtered or unexported fields
}

Processor processes blocks and transactions in order to construct the ledger. It also manages the storage of all block chain data as well as inclusion of new transactions into the transaction queue.

func NewProcessor

func NewProcessor(genesisID BlockID, blockStore BlockStorage, txQueue TransactionQueue, ledger Ledger) *Processor

NewProcessor returns a new Processor instance.

func (*Processor) ProcessBlock

func (p *Processor) ProcessBlock(id BlockID, block *Block, from string) error

ProcessBlock is called to process a new candidate block chain tip.

func (*Processor) ProcessTransaction

func (p *Processor) ProcessTransaction(id TransactionID, tx *Transaction, from string) error

ProcessTransaction is called to process a new candidate transaction for the transaction queue.

func (*Processor) RegisterForNewTransactions

func (p *Processor) RegisterForNewTransactions(ch chan<- NewTx)

RegisterForNewTransactions is called to register to receive notifications of newly queued transactions.

func (*Processor) RegisterForTipChange

func (p *Processor) RegisterForTipChange(ch chan<- TipChange)

RegisterForTipChange is called to register to receive notifications of tip block changes.

func (*Processor) Run

func (p *Processor) Run()

Run executes the Processor's main loop in its own goroutine. It verifies and processes blocks and transactions.

func (*Processor) Shutdown

func (p *Processor) Shutdown()

Shutdown stops the processor synchronously.

func (*Processor) UnregisterForNewTransactions

func (p *Processor) UnregisterForNewTransactions(ch chan<- NewTx)

UnregisterForNewTransactions is called to unregister to receive notifications of newly queued transactions

func (*Processor) UnregisterForTipChange

func (p *Processor) UnregisterForTipChange(ch chan<- TipChange)

UnregisterForTipChange is called to unregister to receive notifications of tip block changes.

type PublicKeyBalance

type PublicKeyBalance struct {
	PublicKey ed25519.PublicKey `json:"public_key"`
	Balance   int64             `json:"balance"`
}

PublicKeyBalance is an entry in the BalancesMessage's Balances field.

type PublicKeyTransactionsMessage

type PublicKeyTransactionsMessage struct {
	PublicKey    ed25519.PublicKey     `json:"public_key"`
	StartHeight  int64                 `json:"start_height"`
	StopHeight   int64                 `json:"stop_height"`
	StopIndex    int                   `json:"stop_index"`
	FilterBlocks []*FilterBlockMessage `json:"filter_blocks"`
	Error        string                `json:"error,omitempty"`
}

PublicKeyTransactionsMessage is used to return a list of block headers and the transactions relevant to the public key over a given height range of the block chain. Type: "public_key_transactions".

type PushTransactionMessage

type PushTransactionMessage struct {
	Transaction *Transaction `json:"transaction"`
}

PushTransactionMessage is used to push a newly processed unconfirmed transaction to peers. Type: "push_transaction".

type PushTransactionResultMessage

type PushTransactionResultMessage struct {
	TransactionID TransactionID `json:"transaction_id"`
	Error         string        `json:"error,omitempty"`
}

PushTransactionResultMessage is sent in response to a PushTransactionMessage. Type: "push_transaction_result".

type Signature

type Signature []byte

Signature is a transaction's signature.

type SubmitWorkMessage

type SubmitWorkMessage struct {
	WorkID int32        `json:"work_id"`
	Header *BlockHeader `json:"header"`
}

SubmitWorkMessage is used by a mining peer to submit a potential solution to the client. Type: "submit_work"

type SubmitWorkResultMessage

type SubmitWorkResultMessage struct {
	WorkID int32  `json:"work_id"`
	Error  string `json:"error,omitempty"`
}

SubmitWorkResultMessage is used to inform a mining peer of the result of its work. Type: "submit_work_result"

type TipChange

type TipChange struct {
	BlockID BlockID // block ID of the main chain tip block
	Block   *Block  // full block
	Source  string  // who sent the block that caused this change
	Connect bool    // true if the tip has been connected. false for disconnected
	More    bool    // true if the tip has been connected and more connections are expected
}

TipChange is a message sent to registered new tip channels on main chain tip (dis-)connection..

type TipHeaderMessage

type TipHeaderMessage struct {
	BlockID     *BlockID     `json:"block_id,omitempty"`
	BlockHeader *BlockHeader `json:"header,omitempty"`
	TimeSeen    int64        `json:"time_seen,omitempty"`
}

TipHeaderMessage is used to send a peer the header for the tip block in the block chain. Type: "tip_header". It is sent in response to the empty "get_tip_header" message type.

type Transaction

type Transaction struct {
	Time      int64             `json:"time"`
	Nonce     int32             `json:"nonce"` // collision prevention. pseudorandom. not used for crypto
	From      ed25519.PublicKey `json:"from"`
	To        ed25519.PublicKey `json:"to"`
	Memo      string            `json:"memo,omitempty"`    // max 100 characters
	Matures   int64             `json:"matures,omitempty"` // block height. if set transaction can't be mined before
	Expires   int64             `json:"expires,omitempty"` // block height. if set transaction can't be mined after
	Series    int64             `json:"series"`            // +1 roughly once a week to allow for pruning history
	Signature Signature         `json:"signature,omitempty"`
}

Transaction represents a ledger citation. It references/mentions one public key by another.

func NewTransaction

func NewTransaction(from, to ed25519.PublicKey, matures, expires, height int64, memo string) *Transaction

NewTransaction returns a new unsigned transaction.

func (Transaction) Contains

func (tx Transaction) Contains(pubKey ed25519.PublicKey) bool

Contains returns true if the transaction is relevant to the given public key.

func (Transaction) ID

func (tx Transaction) ID() (TransactionID, error)

ID computes an ID for a given transaction.

func (Transaction) IsCoinbase

func (tx Transaction) IsCoinbase() bool

IsCoinbase returns true if the transaction is a coinbase. A coinbase is the first transaction in every block used to reward the miner for mining the block.

func (Transaction) IsExpired

func (tx Transaction) IsExpired(height int64) bool

IsExpired returns true if the transaction cannot be mined at the given height.

func (Transaction) IsMature

func (tx Transaction) IsMature(height int64) bool

IsMature returns true if the transaction can be mined at the given height.

func (*Transaction) Sign

func (tx *Transaction) Sign(privKey ed25519.PrivateKey) error

Sign is called to sign a transaction.

func (Transaction) Verify

func (tx Transaction) Verify() (bool, error)

Verify is called to verify only that the transaction is properly signed.

type TransactionID

type TransactionID [32]byte // SHA3-256 hash

TransactionID is a transaction's unique identifier.

func (TransactionID) MarshalJSON

func (id TransactionID) MarshalJSON() ([]byte, error)

MarshalJSON marshals TransactionID as a hex string.

func (TransactionID) String

func (id TransactionID) String() string

String implements the Stringer interface.

func (*TransactionID) UnmarshalJSON

func (id *TransactionID) UnmarshalJSON(b []byte) error

UnmarshalJSON unmarshals a hex string to TransactionID.

type TransactionMessage

type TransactionMessage struct {
	BlockID       *BlockID      `json:"block_id,omitempty"`
	Height        int64         `json:"height,omitempty"`
	TransactionID TransactionID `json:"transaction_id"`
	Transaction   *Transaction  `json:"transaction,omitempty"`
}

TransactionMessage is used to send a peer a confirmed transaction. Type: "transaction"

type TransactionQueue

type TransactionQueue interface {
	// Add adds the transaction to the queue. Returns true if the transaction was added to the queue on this call.
	Add(id TransactionID, tx *Transaction) (bool, error)

	// AddBatch adds a batch of transactions to the queue (a block has been disconnected.)
	// "height" is the block chain height after this disconnection.
	AddBatch(ids []TransactionID, txs []*Transaction, height int64) error

	// RemoveBatch removes a batch of transactions from the queue (a block has been connected.)
	// "height" is the block chain height after this connection.
	// "more" indicates if more connections are coming.
	RemoveBatch(ids []TransactionID, height int64, more bool) error

	// Get returns transactions in the queue for the miner.
	Get(limit int) []*Transaction

	// Exists returns true if the given transaction is in the queue.
	Exists(id TransactionID) bool

	// ExistsSigned returns true if the given transaction is in the queue and contains the given signature.
	ExistsSigned(id TransactionID, signature Signature) bool

	// Len returns the queue length.
	Len() int
}

TransactionQueue is an interface to a queue of transactions to be confirmed.

type TransactionQueueMemory

type TransactionQueueMemory struct {
	// contains filtered or unexported fields
}

TransactionQueueMemory is an in-memory FIFO implementation of the TransactionQueue interface.

func NewTransactionQueueMemory

func NewTransactionQueueMemory(ledger Ledger) *TransactionQueueMemory

NewTransactionQueueMemory returns a new NewTransactionQueueMemory instance.

func (*TransactionQueueMemory) Add

Add adds the transaction to the queue. Returns true if the transaction was added to the queue on this call.

func (*TransactionQueueMemory) AddBatch

func (t *TransactionQueueMemory) AddBatch(ids []TransactionID, txs []*Transaction, height int64) error

AddBatch adds a batch of transactions to the queue (a block has been disconnected.) "height" is the block chain height after this disconnection.

func (*TransactionQueueMemory) Exists

Exists returns true if the given transaction is in the queue.

func (*TransactionQueueMemory) ExistsSigned

func (t *TransactionQueueMemory) ExistsSigned(id TransactionID, signature Signature) bool

ExistsSigned returns true if the given transaction is in the queue and contains the given signature.

func (*TransactionQueueMemory) Get

func (t *TransactionQueueMemory) Get(limit int) []*Transaction

Get returns transactions in the queue for the miner.

func (*TransactionQueueMemory) Len

func (t *TransactionQueueMemory) Len() int

Len returns the queue length.

func (*TransactionQueueMemory) RemoveBatch

func (t *TransactionQueueMemory) RemoveBatch(ids []TransactionID, height int64, more bool) error

RemoveBatch removes a batch of transactions from the queue (a block has been connected.) "height" is the block chain height after this connection. "more" indicates if more connections are coming.

type Wallet

type Wallet struct {
	// contains filtered or unexported fields
}

Wallet manages keys and transactions on behalf of a user.

func NewWallet

func NewWallet(walletDbPath string, recover bool) (*Wallet, error)

NewWallet returns a new Wallet instance.

func (*Wallet) AddFilter

func (w *Wallet) AddFilter(pubKey ed25519.PublicKey) error

AddFilter sends a message to add a public key to the filter.

func (*Wallet) AddKey

func (w *Wallet) AddKey(pubKey ed25519.PublicKey, privKey ed25519.PrivateKey) error

AddKey adds an existing key pair to the database.

func (*Wallet) Connect

func (w *Wallet) Connect(addr string, genesisID BlockID, tlsVerify bool) error

Connect connects to a peer for transaction history, balance information, and sending new transactions. The threat model assumes the peer the wallet is speaking to is not an adversary.

func (*Wallet) GetBalance

func (w *Wallet) GetBalance(pubKey ed25519.PublicKey) (int64, int64, error)

GetBalance returns a public key's balance as well as the current block height.

func (*Wallet) GetBalances

func (w *Wallet) GetBalances(pubKeys []ed25519.PublicKey) ([]PublicKeyBalance, int64, error)

GetBalances returns a set of public key balances as well as the current block height.

func (*Wallet) GetKeys

func (w *Wallet) GetKeys() ([]ed25519.PublicKey, error)

GetKeys returns all of the public keys from the database.

func (*Wallet) GetPrivateKey

func (w *Wallet) GetPrivateKey(pubKey ed25519.PublicKey) (ed25519.PrivateKey, error)

Retrieve a private key for a given public key

func (*Wallet) GetPublicKeyTransactions

func (w *Wallet) GetPublicKeyTransactions(
	pubKey ed25519.PublicKey, startHeight, endHeight int64, startIndex, limit int) (
	startH, stopH int64, stopIndex int, fb []*FilterBlockMessage, err error)

GetPublicKeyTransactions retrieves information about historic transactions involving the given public key.

func (*Wallet) GetTipHeader

func (w *Wallet) GetTipHeader() (BlockID, BlockHeader, error)

GetTipHeader returns the current tip of the main chain's header.

func (*Wallet) GetTransaction

func (w *Wallet) GetTransaction(id TransactionID) (*Transaction, *BlockID, int64, error)

GetTransaction retrieves information about a historic transaction.

func (*Wallet) IsConnected

func (w *Wallet) IsConnected() bool

IsConnected returns true if the wallet is connected to a peer.

func (*Wallet) NewKeys

func (w *Wallet) NewKeys(count int) ([]ed25519.PublicKey, error)

NewKeys generates, encrypts and stores new private keys and returns the public keys.

func (*Wallet) Run

func (w *Wallet) Run()

Run executes the Wallet's main loop in its own goroutine. It manages reading and writing to the peer WebSocket.

func (*Wallet) Send

func (w *Wallet) Send(from, to ed25519.PublicKey, matures, expires int64, memo string) (
	TransactionID, error)

Send creates, signs and pushes a transaction out to the network.

func (*Wallet) SetFilter

func (w *Wallet) SetFilter() error

SetFilter sets the filter for the connection.

func (*Wallet) SetFilterBlockCallback

func (w *Wallet) SetFilterBlockCallback(callback func(*FilterBlockMessage))

SetFilterBlockCallback sets a callback to receive new filter blocks with confirmed transactions relevant to this wallet.

func (*Wallet) SetPassphrase

func (w *Wallet) SetPassphrase(passphrase string) (bool, error)

func (*Wallet) SetTransactionCallback

func (w *Wallet) SetTransactionCallback(callback func(*Transaction))

SetTransactionCallback sets a callback to receive new transactions relevant to the wallet.

func (*Wallet) Shutdown

func (w *Wallet) Shutdown() error

Shutdown is called to shutdown the wallet synchronously.

func (*Wallet) VerifyKey

func (w *Wallet) VerifyKey(pubKey ed25519.PublicKey) error

VerifyKey verifies that the private key associated with the given public key is intact in the database.

type WorkMessage

type WorkMessage struct {
	WorkID  int32        `json:"work_id"`
	Header  *BlockHeader `json:"header"`
	MinTime int64        `json:"min_time"`
	Error   string       `json:"error,omitempty"`
}

WorkMessage is used by a client to send work to perform to a mining peer. The timestamp and nonce in the header can be manipulated by the mining peer. It is the mining peer's responsibility to ensure the timestamp is not set below the minimum timestamp and that the nonce does not exceed MAX_NUMBER (2^53-1). Type: "work"

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL