rosetta-lbry/bitcoin/types.go
2020-09-17 14:40:16 -07:00

493 lines
13 KiB
Go

// Copyright 2020 Coinbase, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bitcoin
import (
"fmt"
"strings"
"github.com/btcsuite/btcd/chaincfg"
"github.com/coinbase/rosetta-sdk-go/types"
)
const (
// Blockchain is Bitcoin.
Blockchain string = "Bitcoin"
// MainnetNetwork is the value of the network
// in MainnetNetworkIdentifier.
MainnetNetwork string = "Mainnet"
// TestnetNetwork is the value of the network
// in TestnetNetworkIdentifier.
TestnetNetwork string = "Testnet3"
// Decimals is the decimals value
// used in Currency.
Decimals = 8
// SatoshisInBitcoin is the number of
// Satoshis in 1 BTC (10^8).
SatoshisInBitcoin = 100000000
// InputOpType is used to describe
// INPUT.
InputOpType = "INPUT"
// OutputOpType is used to describe
// OUTPUT.
OutputOpType = "OUTPUT"
// CoinbaseOpType is used to describe
// Coinbase.
CoinbaseOpType = "COINBASE"
// SuccessStatus is the status of all
// Bitcoin operations because anything
// on-chain is considered successful.
SuccessStatus = "SUCCESS"
// SkippedStatus is the status of all
// operations that are skipped because
// of BIP-30. You can read more about these
// types of operations in BIP-30.
SkippedStatus = "SKIPPED"
// TransactionHashLength is the length
// of any transaction hash in Bitcoin.
TransactionHashLength = 64
// NullData is returned by bitcoind
// as the ScriptPubKey.Type for OP_RETURN
// locking scripts.
NullData = "nulldata"
)
// Fee estimate constants
// Source: https://bitcoinops.org/en/tools/calc-size/
const (
MinFeeRate = float64(0.00001) // nolint:gomnd
TransactionOverhead = 12 // 4 version, 2 segwit flag, 1 vin, 1 vout, 4 lock time
InputSize = 68 // 4 prev index, 32 prev hash, 4 sequence, 1 script size, ~27 script witness
OutputOverhead = 9 // 8 value, 1 script size
P2PKHScriptPubkeySize = 25 // P2PKH size
)
var (
// MainnetGenesisBlockIdentifier is the genesis block for mainnet.
MainnetGenesisBlockIdentifier = &types.BlockIdentifier{
Hash: "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
}
// MainnetParams are the params for mainnet.
MainnetParams = &chaincfg.MainNetParams
// MainnetCurrency is the *types.Currency for mainnet.
MainnetCurrency = &types.Currency{
Symbol: "BTC",
Decimals: Decimals,
}
// TestnetGenesisBlockIdentifier is the genesis block for testnet.
TestnetGenesisBlockIdentifier = &types.BlockIdentifier{
Hash: "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943",
}
// TestnetParams are the params for testnet.
TestnetParams = &chaincfg.TestNet3Params
// TestnetCurrency is the *types.Currency for testnet.
TestnetCurrency = &types.Currency{
Symbol: "tBTC",
Decimals: Decimals,
}
// OperationTypes are all supported operation.Types.
OperationTypes = []string{
InputOpType,
OutputOpType,
CoinbaseOpType,
}
// OperationStatuses are all supported operation.Status.
OperationStatuses = []*types.OperationStatus{
{
Status: SuccessStatus,
Successful: true,
},
{
Status: SkippedStatus,
Successful: false,
},
}
)
// ScriptPubKey is a script placed on the output operations
// of a Bitcoin transaction that must be satisfied to spend
// the output.
type ScriptPubKey struct {
ASM string `json:"asm"`
Hex string `json:"hex"`
RequiredSigs int64 `json:"reqSigs,omitempty"`
Type string `json:"type"`
Addresses []string `json:"addresses,omitempty"`
}
// ScriptSig is a script on the input operations of a
// Bitcoin transaction that satisfies the ScriptPubKey
// on an output being spent.
type ScriptSig struct {
ASM string `json:"asm"`
Hex string `json:"hex"`
}
// BlockchainInfo is information about the Bitcoin network.
// This struct only contains the information necessary for
// this implementation.
type BlockchainInfo struct {
Chain string `json:"chain"`
Blocks int64 `json:"blocks"`
BestBlockHash string `json:"bestblockhash"`
}
// PeerInfo is a collection of relevant info about a particular peer.
type PeerInfo struct {
Addr string `json:"addr"`
Version int64 `json:"version"`
SubVer string `json:"subver"`
StartingHeight int64 `json:"startingheight"`
RelayTxes bool `json:"relaytxes"`
LastSend int64 `json:"lastsend"`
LastRecv int64 `json:"lastrecv"`
BanScore int64 `json:"banscore"`
SyncedBlocks int64 `json:"synced_blocks"`
SyncedHeaders int64 `json:"synced_headers"`
}
// Block is a raw Bitcoin block (with verbosity == 2).
type Block struct {
Hash string `json:"hash"`
Height int64 `json:"height"`
PreviousBlockHash string `json:"previousblockhash"`
Time int64 `json:"time"`
MedianTime int64 `json:"mediantime"`
Nonce int64 `json:"nonce"`
MerkleRoot string `json:"merkleroot"`
Version int32 `json:"version"`
Size int64 `json:"size"`
Weight int64 `json:"weight"`
Bits string `json:"bits"`
Difficulty float64 `json:"difficulty"`
Txs []*Transaction `json:"tx"`
}
// Metadata returns the metadata for a block.
func (b Block) Metadata() (map[string]interface{}, error) {
m := &BlockMetadata{
Nonce: b.Nonce,
MerkleRoot: b.MerkleRoot,
Version: b.Version,
Size: b.Size,
Weight: b.Weight,
MedianTime: b.MedianTime,
Bits: b.Bits,
Difficulty: b.Difficulty,
}
return types.MarshalMap(m)
}
// BlockMetadata is a collection of useful
// metadata in a block.
type BlockMetadata struct {
Nonce int64 `json:"nonce,omitempty"`
MerkleRoot string `json:"merkleroot,omitempty"`
Version int32 `json:"version,omitempty"`
Size int64 `json:"size,omitempty"`
Weight int64 `json:"weight,omitempty"`
MedianTime int64 `json:"mediantime,omitempty"`
Bits string `json:"bits,omitempty"`
Difficulty float64 `json:"difficulty,omitempty"`
}
// Transaction is a raw Bitcoin transaction.
type Transaction struct {
Hex string `json:"hex"`
Hash string `json:"txid"`
Size int64 `json:"size"`
Vsize int64 `json:"vsize"`
Version int32 `json:"version"`
Locktime int64 `json:"locktime"`
Weight int64 `json:"weight"`
Inputs []*Input `json:"vin"`
Outputs []*Output `json:"vout"`
}
// Metadata returns the metadata for a transaction.
func (t Transaction) Metadata() (map[string]interface{}, error) {
m := &TransactionMetadata{
Size: t.Size,
Vsize: t.Vsize,
Version: t.Version,
Locktime: t.Locktime,
Weight: t.Weight,
}
return types.MarshalMap(m)
}
// TransactionMetadata is a collection of useful
// metadata in a transaction.
type TransactionMetadata struct {
Size int64 `json:"size,omitempty"`
Vsize int64 `json:"vsize,omitempty"`
Version int32 `json:"version,omitempty"`
Locktime int64 `json:"locktime,omitempty"`
Weight int64 `json:"weight,omitempty"`
}
// Input is a raw input in a Bitcoin transaction.
type Input struct {
TxHash string `json:"txid"`
Vout int64 `json:"vout"`
ScriptSig *ScriptSig `json:"scriptSig"`
Sequence int64 `json:"sequence"`
TxInWitness []string `json:"txinwitness"`
// Relevant when the input is the coinbase input
Coinbase string `json:"coinbase"`
}
// Metadata returns the metadata for an input.
func (i Input) Metadata() (map[string]interface{}, error) {
m := &OperationMetadata{
ScriptSig: i.ScriptSig,
Sequence: i.Sequence,
TxInWitness: i.TxInWitness,
Coinbase: i.Coinbase,
}
return types.MarshalMap(m)
}
// Output is a raw output in a Bitcoin transaction.
type Output struct {
Value float64 `json:"value"`
Index int64 `json:"n"`
ScriptPubKey *ScriptPubKey `json:"scriptPubKey"`
}
// Metadata returns the metadata for an output.
func (o Output) Metadata() (map[string]interface{}, error) {
m := &OperationMetadata{
ScriptPubKey: o.ScriptPubKey,
}
return types.MarshalMap(m)
}
// OperationMetadata is a collection of useful
// metadata from Bitcoin inputs and outputs.
type OperationMetadata struct {
// Coinbase Metadata
Coinbase string `json:"coinbase,omitempty"`
// Input Metadata
ScriptSig *ScriptSig `json:"scriptsig,omitempty"`
Sequence int64 `json:"sequence,omitempty"`
TxInWitness []string `json:"txinwitness,omitempty"`
// Output Metadata
ScriptPubKey *ScriptPubKey `json:"scriptPubKey,omitempty"`
}
// request represents the JSON-RPC request body
type request struct {
JSONRPC string `json:"jsonrpc"`
ID int `json:"id"`
Method string `json:"method"`
Params []interface{} `json:"params"`
}
func (r request) GetVersion() string { return r.JSONRPC }
func (r request) GetID() int { return r.ID }
func (r request) GetMethod() string { return r.Method }
func (r request) GetParams() []interface{} { return r.Params }
// Responses
// jSONRPCResponse represents an interface for generic JSON-RPC responses
type jSONRPCResponse interface {
Err() error
}
type responseError struct {
Code int64 `json:"code"`
Message string `json:"message"`
}
// BlockResponse is the response body for `getblock` requests
type blockResponse struct {
Result *Block `json:"result"`
Error *responseError `json:"error"`
}
func (b blockResponse) Err() error {
if b.Error == nil {
return nil
}
if b.Error.Code == blockNotFoundErrCode {
return ErrBlockNotFound
}
return fmt.Errorf(
"%w: error JSON RPC response, code: %d, message: %s",
ErrJSONRPCError,
b.Error.Code,
b.Error.Message,
)
}
type pruneBlockchainResponse struct {
Result int64 `json:"result"`
Error *responseError `json:"error"`
}
func (p pruneBlockchainResponse) Err() error {
if p.Error == nil {
return nil
}
return fmt.Errorf(
"%w: error JSON RPC response, code: %d, message: %s",
ErrJSONRPCError,
p.Error.Code,
p.Error.Message,
)
}
type blockchainInfoResponse struct {
Result *BlockchainInfo `json:"result"`
Error *responseError `json:"error"`
}
func (b blockchainInfoResponse) Err() error {
if b.Error == nil {
return nil
}
return fmt.Errorf(
"%w: error JSON RPC response, code: %d, message: %s",
ErrJSONRPCError,
b.Error.Code,
b.Error.Message,
)
}
type peerInfoResponse struct {
Result []*PeerInfo `json:"result"`
Error *responseError `json:"error"`
}
func (p peerInfoResponse) Err() error {
if p.Error == nil {
return nil
}
return fmt.Errorf(
"%w: error JSON RPC response, code: %d, message: %s",
ErrJSONRPCError,
p.Error.Code,
p.Error.Message,
)
}
// blockHashResponse is the response body for `getblockhash` requests
type blockHashResponse struct {
Result string `json:"result"`
Error *responseError `json:"error"`
}
func (b blockHashResponse) Err() error {
if b.Error == nil {
return nil
}
return fmt.Errorf(
"%w: error JSON RPC response, code: %d, message: %s",
ErrJSONRPCError,
b.Error.Code,
b.Error.Message,
)
}
// sendRawTransactionResponse is the response body for `sendrawtransaction` requests
type sendRawTransactionResponse struct {
Result string `json:"result"`
Error *responseError `json:"error"`
}
func (s sendRawTransactionResponse) Err() error {
if s.Error == nil {
return nil
}
return fmt.Errorf(
"%w: error JSON RPC response, code: %d, message: %s",
ErrJSONRPCError,
s.Error.Code,
s.Error.Message,
)
}
type suggestedFeeRate struct {
FeeRate float64 `json:"feerate"`
}
// suggestedFeeRateResponse is the response body for `estimatesmartfee` requests
type suggestedFeeRateResponse struct {
Result *suggestedFeeRate `json:"result"`
Error *responseError `json:"error"`
}
func (s suggestedFeeRateResponse) Err() error {
if s.Error == nil {
return nil
}
return fmt.Errorf(
"%w: error JSON RPC response, code: %d, message: %s",
ErrJSONRPCError,
s.Error.Code,
s.Error.Message,
)
}
// CoinIdentifier converts a tx hash and vout into
// the canonical CoinIdentifier.Identifier used in
// rosetta-bitcoin.
func CoinIdentifier(hash string, vout int64) string {
return fmt.Sprintf("%s:%d", hash, vout)
}
// TransactionHash extracts the transaction hash
// from a CoinIdentifier.Identifier.
func TransactionHash(identifier string) string {
vals := strings.Split(identifier, ":")
return vals[0]
}