Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions graft/coreth/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package_group(
"//tests/reexecute/c",
"//tests/reexecute/chaos",
"//vms/evm/emulate",
"//vms/saevm/cchain/...",
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ARR4N is this an anti-pattern? I could just expose this to the packages as needed rather than for the whole sub-tree.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not an anti-pattern, just a question of what we believe the most appropriate scope to be. The others are only fine-grained because I had my agent (Maru) do it (with his agent). I think we could reasonably allow anything in //tests/... too but there's no need right now.

To me the more important thing is that the other //vms/saevm packages never import the //vms/saevm/cchain ones.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the visibility logic to match graft/coreth.

"//wallet/chain/c",
"//wallet/subnet/primary",
],
Expand Down
10 changes: 10 additions & 0 deletions ids/short.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ func ShortFromString(idStr string) (ShortID, error) {
return ToShortID(bytes)
}

// ShortFromStringOrPanic is the same as ShortFromString, but will panic on
// error.
func ShortFromStringOrPanic(idStr string) ShortID {
Comment thread
JonathanOppenheimer marked this conversation as resolved.
id, err := ShortFromString(idStr)
if err != nil {
panic(err)
}
return id
}

// ShortFromPrefixedString returns a ShortID assuming the cb58 format is
// prefixed
func ShortFromPrefixedString(idStr, prefix string) (ShortID, error) {
Expand Down
44 changes: 44 additions & 0 deletions vms/saevm/cchain/tx/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("//.bazel:defs.bzl", "go_test")

go_library(
name = "tx",
srcs = [
"codec.go",
"export.go",
"import.go",
"tx.go",
],
importpath = "github.com/ava-labs/avalanchego/vms/saevm/cchain/tx",
visibility = ["//visibility:public"],
Comment thread
StephenButtolph marked this conversation as resolved.
Outdated
deps = [
"//codec",
"//codec/linearcodec",
"//ids",
"//utils/hashing",
"//utils/wrappers",
"//vms/components/avax",
"//vms/secp256k1fx",
"@com_github_ava_labs_libevm//common",
],
)

go_test(
name = "tx_test",
srcs = ["tx_test.go"],
data = glob(["testdata/**"]),
embed = [":tx"],
deps = [
"//graft/coreth/plugin/evm/atomic",
"//graft/coreth/plugin/evm/atomic/vm",
"//ids",
"//vms/components/avax",
"//vms/components/verify",
"//vms/secp256k1fx",
"@com_github_ava_labs_libevm//common",
"@com_github_google_go_cmp//cmp",
"@com_github_google_go_cmp//cmp/cmpopts",
"@com_github_stretchr_testify//assert",
"@com_github_stretchr_testify//require",
],
)
47 changes: 47 additions & 0 deletions vms/saevm/cchain/tx/codec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (C) 2019, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package tx

import (
"github.com/ava-labs/avalanchego/codec"
"github.com/ava-labs/avalanchego/codec/linearcodec"
"github.com/ava-labs/avalanchego/utils/wrappers"
"github.com/ava-labs/avalanchego/vms/secp256k1fx"
)

const codecVersion uint16 = 0

var c codec.Manager
Comment thread
StephenButtolph marked this conversation as resolved.

func init() {
c = codec.NewDefaultManager()

// Registration order impacts the typeID included in the canonical format.
// We skip registrations in specific locations so that UTXOs in shared
// memory share the same serialized format as on the P-Chain and X-Chain.
var (
lc = linearcodec.NewDefault()
errs = wrappers.Errs{}
)
errs.Add(
lc.RegisterType(&Import{}),
lc.RegisterType(&Export{}),
)
lc.SkipRegistrations(3)
errs.Add(
lc.RegisterType(&secp256k1fx.TransferInput{}),
)
lc.SkipRegistrations(1)
errs.Add(
lc.RegisterType(&secp256k1fx.TransferOutput{}),
)
lc.SkipRegistrations(1)
errs.Add(
lc.RegisterType(&secp256k1fx.Credential{}),
c.RegisterCodec(codecVersion, lc),
)
if errs.Errored() {
panic(errs.Err)
}
}
38 changes: 38 additions & 0 deletions vms/saevm/cchain/tx/export.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (C) 2019, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package tx

import (
"github.com/ava-labs/libevm/common"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/vms/components/avax"
)

// Export is the unsigned component of a transaction that transfers assets from
// the C-Chain to either the P-Chain or the X-Chain. It modifies the C-Chain
Comment thread
StephenButtolph marked this conversation as resolved.
// state and produces UTXOs in the shared memory between the C-Chain and the
// destination chain.
type Export struct {
NetworkID uint32 `serialize:"true" json:"networkID"`
BlockchainID ids.ID `serialize:"true" json:"blockchainID"`
DestinationChain ids.ID `serialize:"true" json:"destinationChain"`
Ins []Input `serialize:"true" json:"inputs"`
ExportedOutputs []*avax.TransferableOutput `serialize:"true" json:"exportedOutputs"`
}
Comment thread
StephenButtolph marked this conversation as resolved.

// TODO(StephenButtolph): Remove this with its removal from the interface.
func (*Export) isUnsigned() {}

// Input identifies an account + nonce pair on the C-Chain that authorizes the
// asset and quantity to deduct.
//
// If the AssetID is AVAX, the amount will be scaled up to account for the EVM's
// higher denomination.
type Input struct {
Address common.Address `serialize:"true" json:"address"`
Amount uint64 `serialize:"true" json:"amount"`
AssetID ids.ID `serialize:"true" json:"assetID"`
Nonce uint64 `serialize:"true" json:"nonce"`
}
37 changes: 37 additions & 0 deletions vms/saevm/cchain/tx/import.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (C) 2019, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package tx

import (
"github.com/ava-labs/libevm/common"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/vms/components/avax"
)

// Import is the unsigned component of a transaction that transfers assets from
// either the P-Chain or the X-Chain to the C-Chain. It consumes UTXOs in the
// shared memory between the C-Chain and the source chain and increases balances
// in the C-Chain state.
type Import struct {
NetworkID uint32 `serialize:"true" json:"networkID"`
BlockchainID ids.ID `serialize:"true" json:"blockchainID"`
SourceChain ids.ID `serialize:"true" json:"sourceChain"`
ImportedInputs []*avax.TransferableInput `serialize:"true" json:"importedInputs"`
Outs []Output `serialize:"true" json:"outputs"`
}
Comment thread
StephenButtolph marked this conversation as resolved.

// TODO(StephenButtolph): Remove this with its removal from the interface.
func (*Import) isUnsigned() {}

// Output specifies an account on the C-Chain whose balance of the specified
// asset should be increased.
//
// If the AssetID is AVAX, the amount will be scaled up to account for the EVM's
// higher denomination.
type Output struct {
Address common.Address `serialize:"true" json:"address"`
Amount uint64 `serialize:"true" json:"amount"`
AssetID ids.ID `serialize:"true" json:"assetID"`
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x00\x00\x00\x00\x00\x0000000000000000000000000000000000000000000000000000000000000000000000\x00\x00\x00\x0100000000000000000000000000000000000000000000000000000000000000000000\x00\x00\x00\x0500000000\x00\x00\x00\x010000\x00\x00\x00\x01000000000000000000000000000000000000000000000000000000000000\x00\x00\x00\x01\x00\x00\x00\x0500000000\x00\x00\x00\x010000")
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x00\x00\x00\x00\x00\x0000000000000000000000000000000000000000000000000000000000000000000000\x00\x00\x00\x0100000000000000000000000000000000000000000000000000000000000000000000\x00\x00\x00\x0500000000\x00\x00\x00\x010000\x00\x00\x00\x01000000000000000000000000000000000000000000000000000000000000\x00\x00\x00\x01\x00\x00\x00\n\x00\x00\x00\x010000")
100 changes: 100 additions & 0 deletions vms/saevm/cchain/tx/tx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright (C) 2019, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// Package tx defines the Avalanche-specific transaction types used on the
// C-Chain to interact with the shared memory between the C-Chain and other
// chains on the Primary Network.
package tx

import (
"errors"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/hashing"
"github.com/ava-labs/avalanchego/vms/secp256k1fx"
)

// Tx is a signed transaction that interacts with shared memory.
// The [Unsigned] body can be implemented by either [Export] or [Import].
// The [Credential] values are implemented by [secp256k1fx.Credential].
type Tx struct {
Unsigned `serialize:"true" json:"unsignedTx"`
Creds []Credential `serialize:"true" json:"credentials"`
}

// Unsigned is a common interface implemented by [Import] and [Export].
//
// TODO(StephenButtolph): Expand this interface to include UTXO handling,
// verification, and state execution.
type Unsigned interface {
// This function ensures that [Tx.Unsigned] can only be parsed as [Export]
// or [Import].
//
// TODO(StephenButtolph): Once [Unsigned] includes other unexported
// functions, remove this function.
isUnsigned()
}

// Credential is used in [Tx] to authorize an input of a transaction.
//
// It is only implemented by [secp256k1fx.Credential]. An interface must be used
// to correctly produce the canonical binary format during serialization.
type Credential interface {
Self() *secp256k1fx.Credential
}

// ID returns the unique hash of the transaction.
func (t *Tx) ID() ids.ID {
// TODO(StephenButtolph): Optimize ID by caching previously calculated
// values.
bytes, err := t.Bytes()
// This error can happen, but only with invalid transactions. To avoid
// polluting the interface, we represent all invalid transactions with
// the zero ID.
if err != nil {
return ids.ID{}
Comment thread
StephenButtolph marked this conversation as resolved.
}
return hashing.ComputeHash256Array(bytes)
}

// Bytes returns the canonical binary format of the transaction.
func (t *Tx) Bytes() ([]byte, error) {
// TODO(StephenButtolph): Optimize Bytes by caching previously calculated
// values.
return c.Marshal(codecVersion, t)
}

// Parse deserializes a [Tx] from its canonical binary format.
func Parse(b []byte) (*Tx, error) {
var tx Tx
if _, err := c.Unmarshal(b, &tx); err != nil {
return nil, err
}
return &tx, nil
}

// MarshalSlice returns the canonical binary format of a slice of transactions.
func MarshalSlice(txs []*Tx) ([]byte, error) {
Comment thread
JonathanOppenheimer marked this conversation as resolved.
Outdated
if len(txs) == 0 {
return nil, nil
}
return c.Marshal(codecVersion, txs)
}

var errInefficientSlicePacking = errors.New("inefficient slice packing: empty slices should be packed as nil")

// ParseSlice deserializes a slice of [Tx] from its canonical binary format.
func ParseSlice(b []byte) ([]*Tx, error) {
if len(b) == 0 {
return nil, nil
}

var txs []*Tx
if _, err := c.Unmarshal(b, &txs); err != nil {
return nil, err
}
if len(txs) == 0 {
return nil, errInefficientSlicePacking
}
return txs, nil
}
Loading
Loading