Skip to content

Commit

Permalink
Merge pull request #13 from renproject/feat/migrate-to-new-api
Browse files Browse the repository at this point in the history
new API for Multichain
  • Loading branch information
loongy authored Aug 21, 2020
2 parents 391def5 + f9bb31f commit 7bbdebf
Show file tree
Hide file tree
Showing 136 changed files with 4,581 additions and 5,040 deletions.
75 changes: 75 additions & 0 deletions api/account/account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Package account defines the Account API. All chains that use an account-based
// model should implement this API. The Account API is used to send and confirm
// transactions between addresses.
package account

import (
"context"

"github.com/renproject/multichain/api/address"
"github.com/renproject/multichain/api/contract"
"github.com/renproject/pack"
)

// The Tx interfaces defines the functionality that must be exposed by
// account-based transactions.
type Tx interface {
// Hash that uniquely identifies the transaction. Hashes are usually the
// result of an irreversible hashing function applied to some serialized
// representation of the transaction.
Hash() pack.Bytes

// From returns the address from which value is being sent.
From() address.Address

// To returns the address to which value is being sent.
To() address.Address

// Value being sent from one address to another.
Value() pack.U256

// Nonce used to order the transaction with respect to all other
// transactions signed and submitted by the sender of this transaction.
Nonce() pack.U256

// Payload returns arbitrary data that is associated with the transaction.
// This payload is often used to send notes between external accounts, or
// call functions on a contract.
Payload() contract.CallData

// Sighashes that must be signed before the transaction can be submitted by
// the client.
Sighashes() ([]pack.Bytes32, error)

// Sign the transaction by injecting signatures for the required sighashes.
// The serialized public key used to sign the sighashes should also be
// specified whenever it is available.
Sign([]pack.Bytes65, pack.Bytes) error

// Serialize the transaction into bytes. This is the format in which the
// transaction will be submitted by the client.
Serialize() (pack.Bytes, error)
}

// The TxBuilder interface defines the functionality required to build
// account-based transactions. Most chain implementations require additional
// information, and this should be accepted during the construction of the
// chain-specific transaction builder.
type TxBuilder interface {
BuildTx(from, to address.Address, value, nonce pack.U256, payload pack.Bytes) (Tx, error)
}

// The Client interface defines the functionality required to interact with a
// chain over RPC.
type Client interface {
// Tx returns the transaction uniquely identified by the given transaction
// hash. It also returns the number of confirmations for the transaction. If
// the transaction cannot be found before the context is done, or the
// transaction is invalid, then an error should be returned.
Tx(context.Context, pack.Bytes) (Tx, pack.U64, error)

// SubmitTx to the underlying chain. If the transaction cannot be found
// before the context is done, or the transaction is invalid, then an error
// should be returned.
SubmitTx(context.Context, Tx) error
}
67 changes: 67 additions & 0 deletions api/address/address.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Package address defines the Address API. All chains must implement this API,
// so that addresses can be encoded/decoded.
package address

import "github.com/renproject/pack"

// An Address is a human-readable representation of a public identity. It can be
// the address of an external account, contract, or script.
type Address pack.String

// SizeHint returns the number of bytes required to represent the address in
// binary.
func (addr Address) SizeHint() int {
return pack.String(addr).SizeHint()
}

// Marshal the address to binary. You should not call this function directly,
// unless you are implementing marshalling for a container type.
func (addr Address) Marshal(buf []byte, rem int) ([]byte, int, error) {
return pack.String(addr).Marshal(buf, rem)
}

// Unmarshal the address from binary. You should not call this function
// directly, unless you are implementing unmarshalling for a container type.
func (addr *Address) Unmarshal(buf []byte, rem int) ([]byte, int, error) {
return (*pack.String)(addr).Unmarshal(buf, rem)
}

// RawAddress is an address that has been decoded into its binary form.
type RawAddress pack.Bytes

// SizeHint returns the number of bytes required to represent the address in
// binary.
func (addr RawAddress) SizeHint() int {
return pack.Bytes(addr).SizeHint()
}

// Marshal the address to binary. You should not call this function directly,
// unless you are implementing marshalling for a container type.
func (addr RawAddress) Marshal(buf []byte, rem int) ([]byte, int, error) {
return pack.Bytes(addr).Marshal(buf, rem)
}

// Unmarshal the address from binary. You should not call this function
// directly, unless you are implementing unmarshalling for a container type.
func (addr *RawAddress) Unmarshal(buf []byte, rem int) ([]byte, int, error) {
return (*pack.Bytes)(addr).Unmarshal(buf, rem)
}

// The Encoder interface is used to convert raw addresses into human-readable
// addresses.
type Encoder interface {
EncodeAddress(RawAddress) (Address, error)
}

// The Decoder interfaces is used to convert human-readable addresses into raw
// addresses.
type Decoder interface {
DecodeAddress(Address) (RawAddress, error)
}

// The EncoderDecoder interfaces combines encoding and decoding functionality
// into one interface.
type EncoderDecoder interface {
Encoder
Decoder
}
46 changes: 46 additions & 0 deletions api/contract/contract.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Package contract defines the Contract API. All chains that have "smart
// contracts" should implement this API. UTXO-based chains that support
// scripting must not attempt to implementing scripting using this API.
package contract

import (
"context"

"github.com/renproject/multichain/api/address"
"github.com/renproject/pack"
)

// CallData is used to specify a function and its parameters when invoking
// business logic on a contract.
type CallData pack.Bytes

// SizeHint returns the number of bytes required to represent the calldata in
// binary.
func (data CallData) SizeHint() int {
return pack.Bytes(data).SizeHint()
}

// Marshal the address to binary. You should not call this function directly,
// unless you are implementing marshalling for a container type.
func (data CallData) Marshal(buf []byte, rem int) ([]byte, int, error) {
return pack.Bytes(data).Marshal(buf, rem)
}

// Unmarshal the address from binary. You should not call this function
// directly, unless you are implementing unmarshalling for a container type.
func (data *CallData) Unmarshal(buf []byte, rem int) ([]byte, int, error) {
return (*pack.Bytes)(data).Unmarshal(buf, rem)
}

// The Caller interface defines the functionality required to call readonly
// functions on a contract. Calling functions that mutate contract state should
// be done using the Account API.
type Caller interface {
// CallContract at the specified address, using the specified calldata as
// input (this encodes the function and its parameters). The function output
// is returned as raw uninterpreted bytes. It is up to the application to
// interpret these bytes in a meaningful way. If the call cannot be
// completed before the context is done, or the call is invalid, then an
// error should be returned.
CallContract(context.Context, address.Address, CallData) (pack.Bytes, error)
}
25 changes: 25 additions & 0 deletions api/gas/gas.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Package gas defines the Gas API. All chains that support transactions (either
// account-based or utxo-based) should implement this API. This API is used to
// understand the current recommended gas costs required to get confirmations in
// a reasonable amount of time.
package gas

import (
"context"

"github.com/renproject/pack"
)

// The Estimator interface defines the functionality required to know the
// current recommended gas prices.
type Estimator interface {
// EstimateGasPrice that should be used to get confirmation within a
// reasonable amount of time. The precise definition of "reasonable amount
// of time" varies from chain to chain, and so is left open to
// interpretation by the implementation. For example, in Bitcoin, it would
// be the recommended SATs-per-byte required to get a transaction into the
// next block. In Ethereum, it would be the recommended GWEI-per-gas
// required to get a transaction into one of the next few blocks (because
// blocks happen a lot faster).
EstimateGasPrice(context.Context) (pack.U256, error)
}
93 changes: 93 additions & 0 deletions api/utxo/utxo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Package utxo defines the UTXO API. All chains that use a utxo-based model
// should implement this API. The UTXO API is used to send and confirm
// transactions between addresses.
package utxo

import (
"context"

"github.com/renproject/multichain/api/address"
"github.com/renproject/pack"
)

// An Outpoint identifies a specific output produced by a transaction.
type Outpoint struct {
Hash pack.Bytes `json:"hash"`
Index pack.U32 `json:"index"`
}

// An Output is produced by a transaction. It includes the conditions required
// to spend the output (called the pubkey script, based on Bitcoin).
type Output struct {
Outpoint
Value pack.U256 `json:"value"`
PubKeyScript pack.Bytes `json:"pubKeyScript"`
}

// An Input specifies an existing output, produced by a previous transaction, to
// be consumed by another transaction. It includes the script that meets the
// conditions specified by the consumed output (called the sig script, based on
// Bitcoin).
type Input struct {
Output
SigScript pack.Bytes `json:"sigScript"`
}

// A Recipient specifies an address, and an amount, for which a transaction will
// produce an output. Depending on the output, the address can take on different
// formats (e.g. in Bitcoin, addresses can be P2PK, P2PKH, or P2SH).
type Recipient struct {
To address.Address `json:"to"`
Value pack.U256 `json:"value"`
}

// The Tx interfaces defines the functionality that must be exposed by
// utxo-based transactions.
type Tx interface {
// Hash returns the hash that uniquely identifies the transaction.
// Generally, hashes are irreversible hash functions that consume the
// content of the transaction.
Hash() (pack.Bytes, error)

// Inputs consumed by the transaction.
Inputs() ([]Input, error)

// Outputs produced by the transaction.
Outputs() ([]Output, error)

// Sighashes that must be signed before the transaction can be submitted by
// the client.
Sighashes() ([]pack.Bytes32, error)

// Sign the transaction by injecting signatures for the required sighashes.
// The serialized public key used to sign the sighashes should also be
// specified whenever it is available.
Sign([]pack.Bytes65, pack.Bytes) error

// Serialize the transaction into bytes. This is the format in which the
// transaction will be submitted by the client.
Serialize() (pack.Bytes, error)
}

// The TxBuilder interface defines the functionality required to build
// account-based transactions. Most chain implementations require additional
// information, and this should be accepted during the construction of the
// chain-specific transaction builder.
type TxBuilder interface {
BuildTx([]Input, []Recipient) (Tx, error)
}

// The Client interface defines the functionality required to interact with a
// chain over RPC.
type Client interface {
// Output returns the transaction output identified by the given outpoint.
// It also returns the number of confirmations for the output. If the output
// cannot be found before the context is done, or the output is invalid,
// then an error should be returned.
Output(context.Context, Outpoint) (Output, pack.U64, error)

// SubmitTx to the underlying chain. If the transaction cannot be found
// before the context is done, or the transaction is invalid, then an error
// should be returned.
SubmitTx(context.Context, Tx) error
}
39 changes: 34 additions & 5 deletions chain/bitcoin/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,39 @@ import (
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/base58"
"github.com/renproject/multichain/api/address"
"github.com/renproject/pack"
)

type AddressEncoderDecoder struct {
AddressEncoder
AddressDecoder
}

func NewAddressEncoderDecoder(params *chaincfg.Params) AddressEncoderDecoder {
return AddressEncoderDecoder{
AddressEncoder: NewAddressEncoder(params),
AddressDecoder: NewAddressDecoder(params),
}
}

type AddressEncoder struct {
params *chaincfg.Params
}

func NewAddressEncoder(params *chaincfg.Params) AddressEncoder {
return AddressEncoder{params: params}
}

func (encoder AddressEncoder) EncodeAddress(rawAddr address.RawAddress) (address.Address, error) {
encodedAddr := base58.Encode([]byte(rawAddr))
if _, err := btcutil.DecodeAddress(encodedAddr, encoder.params); err != nil {
// Check that the address is valid.
return address.Address(""), err
}
return address.Address(encodedAddr), nil
}

type AddressDecoder struct {
params *chaincfg.Params
}
Expand All @@ -15,11 +45,10 @@ func NewAddressDecoder(params *chaincfg.Params) AddressDecoder {
return AddressDecoder{params: params}
}

func (addrDecoder AddressDecoder) DecodeAddress(encoded pack.String) (pack.Bytes, error) {
addr, err := btcutil.DecodeAddress(string(encoded), addrDecoder.params)
if err != nil {
func (decoder AddressDecoder) DecodeAddress(addr address.Address) (pack.Bytes, error) {
if _, err := btcutil.DecodeAddress(string(addr), decoder.params); err != nil {
// Check that the address is valid.
return nil, err
}
decoded := base58.Decode(addr.EncodeAddress())
return pack.Bytes(decoded), nil
return pack.NewBytes(base58.Decode(string(addr))), nil
}
Loading

0 comments on commit 7bbdebf

Please sign in to comment.