mirror of
https://github.com/luxfi/oraclevm.git
synced 2026-10-30 15:23:15 +00:00
988 lines
29 KiB
Go
988 lines
29 KiB
Go
// Copyright (C) 2023, Ava Labs, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package integration_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"net/http/httptest"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ava-labs/avalanchego/api/metrics"
|
|
"github.com/ava-labs/avalanchego/database/manager"
|
|
"github.com/ava-labs/avalanchego/ids"
|
|
"github.com/ava-labs/avalanchego/snow"
|
|
"github.com/ava-labs/avalanchego/snow/choices"
|
|
"github.com/ava-labs/avalanchego/snow/consensus/snowman"
|
|
"github.com/ava-labs/avalanchego/snow/engine/common"
|
|
"github.com/ava-labs/avalanchego/snow/validators"
|
|
"github.com/ava-labs/avalanchego/utils/crypto/bls"
|
|
"github.com/ava-labs/avalanchego/utils/logging"
|
|
"github.com/ava-labs/avalanchego/utils/set"
|
|
avago_version "github.com/ava-labs/avalanchego/version"
|
|
"github.com/ava-labs/avalanchego/vms/platformvm/warp"
|
|
"github.com/fatih/color"
|
|
ginkgo "github.com/onsi/ginkgo/v2"
|
|
"github.com/onsi/gomega"
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/ava-labs/hypersdk/chain"
|
|
"github.com/ava-labs/hypersdk/codec"
|
|
"github.com/ava-labs/hypersdk/consts"
|
|
"github.com/ava-labs/hypersdk/crypto"
|
|
"github.com/ava-labs/hypersdk/pubsub"
|
|
"github.com/ava-labs/hypersdk/rpc"
|
|
hutils "github.com/ava-labs/hypersdk/utils"
|
|
"github.com/ava-labs/hypersdk/vm"
|
|
|
|
"github.com/bianyuanop/oraclevm/actions"
|
|
"github.com/bianyuanop/oraclevm/auth"
|
|
lconsts "github.com/bianyuanop/oraclevm/consts"
|
|
"github.com/bianyuanop/oraclevm/controller"
|
|
"github.com/bianyuanop/oraclevm/genesis"
|
|
"github.com/bianyuanop/oraclevm/oracle"
|
|
lrpc "github.com/bianyuanop/oraclevm/rpc"
|
|
"github.com/bianyuanop/oraclevm/utils"
|
|
)
|
|
|
|
const transferTxFee = 400 /* base fee */ + 40 /* transfer fee */
|
|
|
|
var (
|
|
logFactory logging.Factory
|
|
log logging.Logger
|
|
)
|
|
|
|
func init() {
|
|
logFactory = logging.NewFactory(logging.Config{
|
|
DisplayLevel: logging.Debug,
|
|
})
|
|
l, err := logFactory.Make("main")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
log = l
|
|
}
|
|
|
|
func TestIntegration(t *testing.T) {
|
|
gomega.RegisterFailHandler(ginkgo.Fail)
|
|
ginkgo.RunSpecs(t, "morpheusvm integration test suites")
|
|
}
|
|
|
|
var (
|
|
requestTimeout time.Duration
|
|
vms int
|
|
minPrice int64
|
|
)
|
|
|
|
func init() {
|
|
flag.DurationVar(
|
|
&requestTimeout,
|
|
"request-timeout",
|
|
120*time.Second,
|
|
"timeout for transaction issuance and confirmation",
|
|
)
|
|
flag.IntVar(
|
|
&vms,
|
|
"vms",
|
|
3,
|
|
"number of VMs to create",
|
|
)
|
|
flag.Int64Var(
|
|
&minPrice,
|
|
"min-price",
|
|
-1,
|
|
"minimum price",
|
|
)
|
|
}
|
|
|
|
var (
|
|
priv crypto.PrivateKey
|
|
factory *auth.ED25519Factory
|
|
rsender crypto.PublicKey
|
|
sender string
|
|
|
|
priv2 crypto.PrivateKey
|
|
factory2 *auth.ED25519Factory
|
|
rsender2 crypto.PublicKey
|
|
sender2 string
|
|
|
|
priv3 crypto.PrivateKey
|
|
factory3 *auth.ED25519Factory
|
|
rsender3 crypto.PublicKey
|
|
sender3 string
|
|
|
|
// when used with embedded VMs
|
|
genesisBytes []byte
|
|
instances []instance
|
|
|
|
networkID uint32
|
|
gen *genesis.Genesis
|
|
)
|
|
|
|
type instance struct {
|
|
chainID ids.ID
|
|
nodeID ids.NodeID
|
|
vm *vm.VM
|
|
toEngine chan common.Message
|
|
JSONRPCServer *httptest.Server
|
|
BaseJSONRPCServer *httptest.Server
|
|
WebSocketServer *httptest.Server
|
|
cli *rpc.JSONRPCClient // clients for embedded VMs
|
|
lcli *lrpc.JSONRPCClient
|
|
}
|
|
|
|
var _ = ginkgo.BeforeSuite(func() {
|
|
log.Info("VMID", zap.Stringer("id", lconsts.ID))
|
|
gomega.Ω(vms).Should(gomega.BeNumerically(">", 1))
|
|
|
|
var err error
|
|
priv, err = crypto.GeneratePrivateKey()
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
factory = auth.NewED25519Factory(priv)
|
|
rsender = priv.PublicKey()
|
|
sender = utils.Address(rsender)
|
|
log.Debug(
|
|
"generated key",
|
|
zap.String("addr", sender),
|
|
zap.String("pk", hex.EncodeToString(priv[:])),
|
|
)
|
|
|
|
priv2, err = crypto.GeneratePrivateKey()
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
factory2 = auth.NewED25519Factory(priv2)
|
|
rsender2 = priv2.PublicKey()
|
|
sender2 = utils.Address(rsender2)
|
|
log.Debug(
|
|
"generated key",
|
|
zap.String("addr", sender2),
|
|
zap.String("pk", hex.EncodeToString(priv2[:])),
|
|
)
|
|
|
|
priv3, err = crypto.GeneratePrivateKey()
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
factory3 = auth.NewED25519Factory(priv3)
|
|
rsender3 = priv3.PublicKey()
|
|
sender3 = utils.Address(rsender3)
|
|
log.Debug(
|
|
"generated key",
|
|
zap.String("addr", sender3),
|
|
zap.String("pk", hex.EncodeToString(priv3[:])),
|
|
)
|
|
|
|
// create embedded VMs
|
|
instances = make([]instance, vms)
|
|
|
|
gen = genesis.Default()
|
|
if minPrice >= 0 {
|
|
gen.MinUnitPrice = uint64(minPrice)
|
|
}
|
|
gen.MinBlockGap = 0
|
|
gen.CustomAllocation = []*genesis.CustomAllocation{
|
|
{
|
|
Address: sender,
|
|
Balance: 10_000_000,
|
|
},
|
|
}
|
|
genesisBytes, err = json.Marshal(gen)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
|
|
networkID = uint32(1)
|
|
subnetID := ids.GenerateTestID()
|
|
chainID := ids.GenerateTestID()
|
|
|
|
app := &appSender{}
|
|
for i := range instances {
|
|
nodeID := ids.GenerateTestNodeID()
|
|
sk, err := bls.NewSecretKey()
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
l, err := logFactory.Make(nodeID.String())
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
dname, err := os.MkdirTemp("", fmt.Sprintf("%s-chainData", nodeID.String()))
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
snowCtx := &snow.Context{
|
|
NetworkID: networkID,
|
|
SubnetID: subnetID,
|
|
ChainID: chainID,
|
|
NodeID: nodeID,
|
|
Log: l,
|
|
ChainDataDir: dname,
|
|
Metrics: metrics.NewOptionalGatherer(),
|
|
PublicKey: bls.PublicFromSecretKey(sk),
|
|
WarpSigner: warp.NewSigner(sk, networkID, chainID),
|
|
ValidatorState: &validators.TestState{},
|
|
}
|
|
|
|
toEngine := make(chan common.Message, 1)
|
|
db := manager.NewMemDB(avago_version.CurrentDatabase)
|
|
|
|
v := controller.New()
|
|
err = v.Initialize(
|
|
context.TODO(),
|
|
snowCtx,
|
|
db,
|
|
genesisBytes,
|
|
nil,
|
|
[]byte(
|
|
`{"parallelism":3, "testMode":true, "logLevel":"debug", "trackedStocks": ["AMD", "Apple"]}`,
|
|
),
|
|
toEngine,
|
|
nil,
|
|
app,
|
|
)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
|
|
var hd map[string]*common.HTTPHandler
|
|
hd, err = v.CreateHandlers(context.TODO())
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
|
|
jsonRPCServer := httptest.NewServer(hd[rpc.JSONRPCEndpoint].Handler)
|
|
ljsonRPCServer := httptest.NewServer(hd[lrpc.JSONRPCEndpoint].Handler)
|
|
webSocketServer := httptest.NewServer(hd[rpc.WebSocketEndpoint].Handler)
|
|
instances[i] = instance{
|
|
chainID: snowCtx.ChainID,
|
|
nodeID: snowCtx.NodeID,
|
|
vm: v,
|
|
toEngine: toEngine,
|
|
JSONRPCServer: jsonRPCServer,
|
|
BaseJSONRPCServer: ljsonRPCServer,
|
|
WebSocketServer: webSocketServer,
|
|
cli: rpc.NewJSONRPCClient(jsonRPCServer.URL),
|
|
lcli: lrpc.NewJSONRPCClient(ljsonRPCServer.URL, snowCtx.NetworkID, snowCtx.ChainID),
|
|
}
|
|
|
|
// Force sync ready (to mimic bootstrapping from genesis)
|
|
v.ForceReady()
|
|
}
|
|
|
|
// Verify genesis allocations loaded correctly (do here otherwise test may
|
|
// check during and it will be inaccurate)
|
|
for _, inst := range instances {
|
|
cli := inst.lcli
|
|
g, err := cli.Genesis(context.Background())
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
|
|
csupply := uint64(0)
|
|
for _, alloc := range g.CustomAllocation {
|
|
balance, err := cli.Balance(context.Background(), alloc.Address)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
gomega.Ω(balance).Should(gomega.Equal(alloc.Balance))
|
|
csupply += alloc.Balance
|
|
}
|
|
}
|
|
|
|
app.instances = instances
|
|
color.Blue("created %d VMs", vms)
|
|
})
|
|
|
|
var _ = ginkgo.AfterSuite(func() {
|
|
for _, iv := range instances {
|
|
iv.JSONRPCServer.Close()
|
|
iv.BaseJSONRPCServer.Close()
|
|
iv.WebSocketServer.Close()
|
|
err := iv.vm.Shutdown(context.TODO())
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
}
|
|
})
|
|
|
|
var _ = ginkgo.Describe("[Ping]", func() {
|
|
ginkgo.It("can ping", func() {
|
|
for _, inst := range instances {
|
|
cli := inst.cli
|
|
ok, err := cli.Ping(context.Background())
|
|
gomega.Ω(ok).Should(gomega.BeTrue())
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
}
|
|
})
|
|
})
|
|
|
|
var _ = ginkgo.Describe("[Network]", func() {
|
|
ginkgo.It("can get network", func() {
|
|
for _, inst := range instances {
|
|
cli := inst.cli
|
|
networkID, subnetID, chainID, err := cli.Network(context.Background())
|
|
gomega.Ω(networkID).Should(gomega.Equal(uint32(1)))
|
|
gomega.Ω(subnetID).ShouldNot(gomega.Equal(ids.Empty))
|
|
gomega.Ω(chainID).ShouldNot(gomega.Equal(ids.Empty))
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
}
|
|
})
|
|
})
|
|
|
|
var _ = ginkgo.Describe("[Tx Processing]", func() {
|
|
ginkgo.It("get currently accepted block ID", func() {
|
|
for _, inst := range instances {
|
|
cli := inst.cli
|
|
_, _, _, err := cli.Accepted(context.Background())
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
}
|
|
})
|
|
|
|
var transferTxRoot *chain.Transaction
|
|
ginkgo.It("Gossip TransferTx to a different node", func() {
|
|
ginkgo.By("issue TransferTx", func() {
|
|
parser, err := instances[0].lcli.Parser(context.Background())
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
submit, transferTx, _, err := instances[0].cli.GenerateTransaction(
|
|
context.Background(),
|
|
parser,
|
|
nil,
|
|
&actions.Transfer{
|
|
To: rsender2,
|
|
Value: 100_000, // must be more than StateLockup
|
|
},
|
|
factory,
|
|
)
|
|
transferTxRoot = transferTx
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
gomega.Ω(submit(context.Background())).Should(gomega.BeNil())
|
|
gomega.Ω(instances[0].vm.Mempool().Len(context.Background())).Should(gomega.Equal(1))
|
|
})
|
|
|
|
ginkgo.By("skip duplicate", func() {
|
|
_, err := instances[0].cli.SubmitTx(
|
|
context.Background(),
|
|
transferTxRoot.Bytes(),
|
|
)
|
|
gomega.Ω(err).To(gomega.Not(gomega.BeNil()))
|
|
})
|
|
|
|
ginkgo.By("send gossip from node 0 to 1", func() {
|
|
err := instances[0].vm.Gossiper().ForceGossip(context.TODO())
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
})
|
|
|
|
ginkgo.By("skip invalid time", func() {
|
|
tx := chain.NewTx(
|
|
&chain.Base{
|
|
ChainID: instances[0].chainID,
|
|
Timestamp: 0,
|
|
UnitPrice: 1000,
|
|
},
|
|
nil,
|
|
&actions.Transfer{
|
|
To: rsender2,
|
|
Value: 110,
|
|
},
|
|
)
|
|
// Must do manual construction to avoid `tx.Sign` error (would fail with
|
|
// 0 timestamp)
|
|
msg, err := tx.Digest()
|
|
gomega.Ω(err).To(gomega.BeNil())
|
|
auth, err := factory.Sign(msg, tx.Action)
|
|
gomega.Ω(err).To(gomega.BeNil())
|
|
tx.Auth = auth
|
|
p := codec.NewWriter(0, consts.MaxInt) // test codec growth
|
|
gomega.Ω(tx.Marshal(p)).To(gomega.BeNil())
|
|
gomega.Ω(p.Err()).To(gomega.BeNil())
|
|
_, err = instances[0].cli.SubmitTx(
|
|
context.Background(),
|
|
p.Bytes(),
|
|
)
|
|
gomega.Ω(err).To(gomega.Not(gomega.BeNil()))
|
|
})
|
|
|
|
ginkgo.By("skip duplicate (after gossip, which shouldn't clear)", func() {
|
|
_, err := instances[0].cli.SubmitTx(
|
|
context.Background(),
|
|
transferTxRoot.Bytes(),
|
|
)
|
|
gomega.Ω(err).To(gomega.Not(gomega.BeNil()))
|
|
})
|
|
|
|
ginkgo.By("receive gossip in the node 1, and signal block build", func() {
|
|
instances[1].vm.Builder().ForceNotify()
|
|
<-instances[1].toEngine
|
|
})
|
|
|
|
ginkgo.By("build block in the node 1", func() {
|
|
ctx := context.TODO()
|
|
blk, err := instances[1].vm.BuildBlock(ctx)
|
|
gomega.Ω(err).To(gomega.BeNil())
|
|
|
|
gomega.Ω(blk.Verify(ctx)).To(gomega.BeNil())
|
|
gomega.Ω(blk.Status()).To(gomega.Equal(choices.Processing))
|
|
|
|
err = instances[1].vm.SetPreference(ctx, blk.ID())
|
|
gomega.Ω(err).To(gomega.BeNil())
|
|
|
|
gomega.Ω(blk.Accept(ctx)).To(gomega.BeNil())
|
|
gomega.Ω(blk.Status()).To(gomega.Equal(choices.Accepted))
|
|
|
|
lastAccepted, err := instances[1].vm.LastAccepted(ctx)
|
|
gomega.Ω(err).To(gomega.BeNil())
|
|
gomega.Ω(lastAccepted).To(gomega.Equal(blk.ID()))
|
|
|
|
results := blk.(*chain.StatelessBlock).Results()
|
|
gomega.Ω(results).Should(gomega.HaveLen(1))
|
|
gomega.Ω(results[0].Success).Should(gomega.BeTrue())
|
|
gomega.Ω(results[0].Units).Should(gomega.Equal(uint64(transferTxFee)))
|
|
gomega.Ω(results[0].Output).Should(gomega.BeNil())
|
|
})
|
|
|
|
ginkgo.By("ensure balance is updated", func() {
|
|
balance, err := instances[1].lcli.Balance(context.Background(), sender)
|
|
gomega.Ω(err).To(gomega.BeNil())
|
|
gomega.Ω(balance).To(gomega.Equal(uint64(9899560)))
|
|
balance2, err := instances[1].lcli.Balance(context.Background(), sender2)
|
|
gomega.Ω(err).To(gomega.BeNil())
|
|
gomega.Ω(balance2).To(gomega.Equal(uint64(100000)))
|
|
})
|
|
})
|
|
|
|
ginkgo.It("ensure multiple txs work ", func() {
|
|
ginkgo.By("transfer funds again", func() {
|
|
parser, err := instances[1].lcli.Parser(context.Background())
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
submit, _, _, err := instances[1].cli.GenerateTransaction(
|
|
context.Background(),
|
|
parser,
|
|
nil,
|
|
&actions.Transfer{
|
|
To: rsender2,
|
|
Value: 101,
|
|
},
|
|
factory,
|
|
)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
gomega.Ω(submit(context.Background())).Should(gomega.BeNil())
|
|
accept := expectBlk(instances[1])
|
|
results := accept()
|
|
gomega.Ω(results).Should(gomega.HaveLen(1))
|
|
gomega.Ω(results[0].Success).Should(gomega.BeTrue())
|
|
|
|
balance2, err := instances[1].lcli.Balance(context.Background(), sender2)
|
|
gomega.Ω(err).To(gomega.BeNil())
|
|
gomega.Ω(balance2).To(gomega.Equal(uint64(100101)))
|
|
})
|
|
})
|
|
|
|
ginkgo.It("Test processing block handling", func() {
|
|
var accept, accept2 func() []*chain.Result
|
|
|
|
ginkgo.By("create processing tip", func() {
|
|
parser, err := instances[1].lcli.Parser(context.Background())
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
submit, _, _, err := instances[1].cli.GenerateTransaction(
|
|
context.Background(),
|
|
parser,
|
|
nil,
|
|
&actions.Transfer{
|
|
To: rsender2,
|
|
Value: 200,
|
|
},
|
|
factory,
|
|
)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
gomega.Ω(submit(context.Background())).Should(gomega.BeNil())
|
|
accept = expectBlk(instances[1])
|
|
|
|
submit, _, _, err = instances[1].cli.GenerateTransaction(
|
|
context.Background(),
|
|
parser,
|
|
nil,
|
|
&actions.Transfer{
|
|
To: rsender2,
|
|
Value: 201,
|
|
},
|
|
factory,
|
|
)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
gomega.Ω(submit(context.Background())).Should(gomega.BeNil())
|
|
accept2 = expectBlk(instances[1])
|
|
})
|
|
|
|
ginkgo.By("clear processing tip", func() {
|
|
results := accept()
|
|
gomega.Ω(results).Should(gomega.HaveLen(1))
|
|
gomega.Ω(results[0].Success).Should(gomega.BeTrue())
|
|
results = accept2()
|
|
gomega.Ω(results).Should(gomega.HaveLen(1))
|
|
gomega.Ω(results[0].Success).Should(gomega.BeTrue())
|
|
})
|
|
})
|
|
|
|
ginkgo.It("ensure mempool works", func() {
|
|
ginkgo.By("fail Gossip TransferTx to a stale node when missing previous blocks", func() {
|
|
parser, err := instances[1].lcli.Parser(context.Background())
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
submit, _, _, err := instances[1].cli.GenerateTransaction(
|
|
context.Background(),
|
|
parser,
|
|
nil,
|
|
&actions.Transfer{
|
|
To: rsender2,
|
|
Value: 203,
|
|
},
|
|
factory,
|
|
)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
gomega.Ω(submit(context.Background())).Should(gomega.BeNil())
|
|
|
|
err = instances[1].vm.Gossiper().ForceGossip(context.TODO())
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
|
|
// mempool in 0 should be 1 (old amount), since gossip/submit failed
|
|
gomega.Ω(instances[0].vm.Mempool().Len(context.TODO())).Should(gomega.Equal(1))
|
|
})
|
|
})
|
|
|
|
ginkgo.It("ensure unprocessed tip works", func() {
|
|
ginkgo.By("import accepted blocks to instance 2", func() {
|
|
ctx := context.TODO()
|
|
o := instances[1]
|
|
blks := []snowman.Block{}
|
|
next, err := o.vm.LastAccepted(ctx)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
for {
|
|
blk, err := o.vm.GetBlock(ctx, next)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
blks = append([]snowman.Block{blk}, blks...)
|
|
if blk.Height() == 1 {
|
|
break
|
|
}
|
|
next = blk.Parent()
|
|
}
|
|
|
|
n := instances[2]
|
|
blk1, err := n.vm.ParseBlock(ctx, blks[0].Bytes())
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
err = blk1.Verify(ctx)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
|
|
// Parse tip
|
|
blk2, err := n.vm.ParseBlock(ctx, blks[1].Bytes())
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
blk3, err := n.vm.ParseBlock(ctx, blks[2].Bytes())
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
|
|
// Verify tip
|
|
err = blk2.Verify(ctx)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
err = blk3.Verify(ctx)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
|
|
// Accept tip
|
|
err = blk1.Accept(ctx)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
err = blk2.Accept(ctx)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
err = blk3.Accept(ctx)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
|
|
// Parse another
|
|
blk4, err := n.vm.ParseBlock(ctx, blks[3].Bytes())
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
err = blk4.Verify(ctx)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
err = blk4.Accept(ctx)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
gomega.Ω(n.vm.SetPreference(ctx, blk4.ID())).Should(gomega.BeNil())
|
|
})
|
|
})
|
|
|
|
ginkgo.It("processes valid index transactions (w/block listening)", func() {
|
|
// Clear previous txs on instance 0
|
|
accept := expectBlk(instances[0])
|
|
accept() // don't care about results
|
|
|
|
// Subscribe to blocks
|
|
cli, err := rpc.NewWebSocketClient(instances[0].WebSocketServer.URL, rpc.DefaultHandshakeTimeout, pubsub.MaxPendingMessages, pubsub.MaxReadMessageSize)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
gomega.Ω(cli.RegisterBlocks()).Should(gomega.BeNil())
|
|
|
|
// Wait for message to be sent
|
|
time.Sleep(2 * pubsub.MaxMessageWait)
|
|
|
|
// Fetch balances
|
|
balance, err := instances[0].lcli.Balance(context.TODO(), sender)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
|
|
// Send tx
|
|
other, err := crypto.GeneratePrivateKey()
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
transfer := &actions.Transfer{
|
|
To: other.PublicKey(),
|
|
Value: 1,
|
|
}
|
|
|
|
parser, err := instances[0].lcli.Parser(context.Background())
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
submit, rawTx, _, err := instances[0].cli.GenerateTransaction(
|
|
context.Background(),
|
|
parser,
|
|
nil,
|
|
transfer,
|
|
factory,
|
|
)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
gomega.Ω(submit(context.Background())).Should(gomega.BeNil())
|
|
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
accept = expectBlk(instances[0])
|
|
results := accept()
|
|
gomega.Ω(results).Should(gomega.HaveLen(1))
|
|
gomega.Ω(results[0].Success).Should(gomega.BeTrue())
|
|
|
|
// Read item from connection
|
|
blk, lresults, err := cli.ListenBlock(context.TODO(), parser)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
gomega.Ω(len(blk.Txs)).Should(gomega.Equal(1))
|
|
tx := blk.Txs[0].Action.(*actions.Transfer)
|
|
gomega.Ω(tx.Value).To(gomega.Equal(uint64(1)))
|
|
gomega.Ω(lresults).Should(gomega.Equal(results))
|
|
|
|
// Check balance modifications are correct
|
|
balancea, err := instances[0].lcli.Balance(context.TODO(), sender)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
g, err := instances[0].lcli.Genesis(context.TODO())
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
r := g.Rules(time.Now().UnixMilli(), networkID, instances[0].chainID)
|
|
maxUnits, err := rawTx.MaxUnits(r)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
gomega.Ω(balance).Should(gomega.Equal(balancea + maxUnits + 1))
|
|
|
|
// Close connection when done
|
|
gomega.Ω(cli.Close()).Should(gomega.BeNil())
|
|
})
|
|
|
|
ginkgo.It("processes valid index transactions (w/streaming verification)", func() {
|
|
// Create streaming client
|
|
cli, err := rpc.NewWebSocketClient(instances[0].WebSocketServer.URL, rpc.DefaultHandshakeTimeout, pubsub.MaxPendingMessages, pubsub.MaxReadMessageSize)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
|
|
// Create tx
|
|
other, err := crypto.GeneratePrivateKey()
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
transfer := &actions.Transfer{
|
|
To: other.PublicKey(),
|
|
Value: 1,
|
|
}
|
|
parser, err := instances[0].lcli.Parser(context.Background())
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
_, tx, _, err := instances[0].cli.GenerateTransaction(
|
|
context.Background(),
|
|
parser,
|
|
nil,
|
|
transfer,
|
|
factory,
|
|
)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
|
|
// Submit tx and accept block
|
|
gomega.Ω(cli.RegisterTx(tx)).Should(gomega.BeNil())
|
|
|
|
// Wait for message to be sent
|
|
time.Sleep(2 * pubsub.MaxMessageWait)
|
|
|
|
for instances[0].vm.Mempool().Len(context.TODO()) == 0 {
|
|
// We need to wait for mempool to be populated because issuance will
|
|
// return as soon as bytes are on the channel.
|
|
hutils.Outf("{{yellow}}waiting for mempool to return non-zero txs{{/}}\n")
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
accept := expectBlk(instances[0])
|
|
results := accept()
|
|
gomega.Ω(results).Should(gomega.HaveLen(1))
|
|
gomega.Ω(results[0].Success).Should(gomega.BeTrue())
|
|
|
|
// Read decision from connection
|
|
txID, dErr, result, err := cli.ListenTx(context.TODO())
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
gomega.Ω(txID).Should(gomega.Equal(tx.ID()))
|
|
gomega.Ω(dErr).Should(gomega.BeNil())
|
|
gomega.Ω(result.Success).Should(gomega.BeTrue())
|
|
gomega.Ω(result).Should(gomega.Equal(results[0]))
|
|
|
|
// Close connection when done
|
|
gomega.Ω(cli.Close()).Should(gomega.BeNil())
|
|
})
|
|
|
|
// ginkgo.It("test rpc methods work", func() {
|
|
// history, err := instances[0].lcli.AggregationHistory(context.TODO(), 0, 20)
|
|
// gomega.Ω(err).Should(gomega.BeNil())
|
|
// gomega.Ω(history).Should(gomega.HaveLen(0))
|
|
// })
|
|
|
|
ginkgo.It("ensure oracle configuration is properly loaded", func() {
|
|
metas, err := instances[0].lcli.AvailableEntities(context.TODO())
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
gomega.Ω(metas).Should(gomega.HaveLen(2))
|
|
|
|
amd := &oracle.EntityCollectionMeta{
|
|
EntityName: "AMD",
|
|
EntityID: 0,
|
|
EntityType: oracle.StockID,
|
|
}
|
|
|
|
apple := &oracle.EntityCollectionMeta{
|
|
EntityName: "Apple",
|
|
EntityID: 1,
|
|
EntityType: oracle.StockID,
|
|
}
|
|
|
|
gomega.Ω(metas[0].EntityID).Should(gomega.Equal(amd.EntityID))
|
|
gomega.Ω(metas[0].EntityName).Should(gomega.Equal(amd.EntityName))
|
|
gomega.Ω(metas[0].EntityType).Should(gomega.Equal(amd.EntityType))
|
|
|
|
gomega.Ω(metas[1].EntityID).Should(gomega.Equal(apple.EntityID))
|
|
gomega.Ω(metas[1].EntityName).Should(gomega.Equal(apple.EntityName))
|
|
gomega.Ω(metas[1].EntityType).Should(gomega.Equal(apple.EntityType))
|
|
})
|
|
|
|
ginkgo.It("testing functionality of entity execution", func() {
|
|
parser, err := instances[0].lcli.Parser(context.Background())
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
submit, _, _, err := instances[0].cli.GenerateTransaction(
|
|
context.Background(),
|
|
parser,
|
|
nil,
|
|
&actions.UploadEntity{
|
|
EntityIndex: 0,
|
|
EntityType: 0,
|
|
Payload: []byte(`{ "ticker": "AMD", "price": 1999 }`),
|
|
},
|
|
factory,
|
|
)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
gomega.Ω(submit(context.Background())).Should(gomega.BeNil())
|
|
|
|
accept := expectBlk(instances[0])
|
|
results := accept()
|
|
gomega.Ω(results).Should(gomega.HaveLen(1))
|
|
output := results[0].Output
|
|
entityWithMeta, err := oracle.UnmarshalEntityWithMeta(output)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
|
|
s, ok := entityWithMeta.Entity.(*oracle.Stock)
|
|
gomega.Ω(ok).Should(gomega.BeTrue())
|
|
gomega.Ω(s.Price).Should(gomega.Equal(uint64(1999)))
|
|
gomega.Ω(s.Ticker).Should(gomega.Equal("AMD"))
|
|
|
|
time.Sleep(2 * time.Second)
|
|
|
|
count, err := instances[0].lcli.CollectionCount(context.TODO(), 0)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
gomega.Ω(count).Should(gomega.Equal(uint64(1)))
|
|
|
|
history, length, err := instances[0].lcli.AggregationHistory(context.TODO(), 0, 1)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
gomega.Ω(length).Should(gomega.Equal(1))
|
|
|
|
stock, err := oracle.UnmarshalStock(history[0])
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
gomega.Ω(stock.Price).Should(gomega.Equal(uint64(1999)))
|
|
gomega.Ω(stock.Ticker).Should(gomega.Equal("AMD"))
|
|
})
|
|
|
|
ginkgo.It("test functionality of stock aggregation", func() {
|
|
ginkgo.By("ensure there is already one aggregation result in there", func() {
|
|
historyLen, err := instances[0].lcli.CollectionCount(context.TODO(), 0)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
gomega.Ω(historyLen).Should(gomega.Equal(uint64(1)))
|
|
})
|
|
|
|
ginkgo.By("submitting two transactions", func() {
|
|
parser, err := instances[0].lcli.Parser(context.Background())
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
submit, _, _, err := instances[0].cli.GenerateTransaction(
|
|
context.Background(),
|
|
parser,
|
|
nil,
|
|
&actions.UploadEntity{
|
|
EntityIndex: 0,
|
|
EntityType: 0,
|
|
Payload: []byte(`{ "ticker": "AMD", "price": 20 }`),
|
|
},
|
|
factory,
|
|
)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
gomega.Ω(submit(context.Background())).Should(gomega.BeNil())
|
|
|
|
submit2, _, _, err2 := instances[0].cli.GenerateTransaction(
|
|
context.Background(),
|
|
parser,
|
|
nil,
|
|
&actions.UploadEntity{
|
|
EntityIndex: 0,
|
|
EntityType: 0,
|
|
Payload: []byte(`{ "ticker": "AMD", "price": 30 }`),
|
|
},
|
|
factory,
|
|
)
|
|
gomega.Ω(err2).Should(gomega.BeNil())
|
|
gomega.Ω(submit2(context.Background())).Should(gomega.BeNil())
|
|
})
|
|
|
|
ginkgo.By("build block and check aggregation result", func() {
|
|
accept := expectBlk(instances[0])
|
|
results := accept()
|
|
gomega.Ω(results).Should(gomega.HaveLen(2))
|
|
time.Sleep(2 * time.Second)
|
|
|
|
historyLen, err := instances[0].lcli.CollectionCount(context.TODO(), 0)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
gomega.Ω(historyLen).Should(gomega.Equal(uint64(2)))
|
|
|
|
history, length, err := instances[0].lcli.AggregationHistory(context.TODO(), 0, 1)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
gomega.Ω(length).Should(gomega.Equal(1))
|
|
|
|
stock, err := oracle.UnmarshalStock(history[0])
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
gomega.Ω(stock.Price).Should(gomega.Equal(uint64(25)))
|
|
gomega.Ω(stock.Ticker).Should(gomega.Equal("AMD"))
|
|
})
|
|
})
|
|
|
|
ginkgo.It("test warp query", func() {
|
|
ginkgo.By("submit and build block to aggregate feeds", func() {
|
|
parser, err := instances[0].lcli.Parser(context.Background())
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
submit, _, _, err := instances[0].cli.GenerateTransaction(
|
|
context.Background(),
|
|
parser,
|
|
nil,
|
|
&actions.UploadEntity{
|
|
EntityIndex: 0,
|
|
EntityType: 0,
|
|
Payload: []byte(`{ "ticker": "AMD", "price": 999 }`),
|
|
},
|
|
factory,
|
|
)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
gomega.Ω(submit(context.Background())).Should(gomega.BeNil())
|
|
accept := expectBlk(instances[0])
|
|
results := accept()
|
|
gomega.Ω(results).Should(gomega.HaveLen(1))
|
|
})
|
|
|
|
ginkgo.By("submit query transaction", func() {
|
|
wq := &actions.WarpQuery{
|
|
EntityIndex: 0,
|
|
DestinationChainID: ids.GenerateTestID(),
|
|
}
|
|
wtb, err := wq.Marshal()
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
uwm, err := warp.NewUnsignedMessage(networkID, ids.Empty, wtb)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
wm, err := warp.NewMessage(uwm, &warp.BitSetSignature{})
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
tx := chain.NewTx(
|
|
&chain.Base{
|
|
ChainID: instances[0].chainID,
|
|
Timestamp: hutils.UnixRMilli(-1, 5*consts.MillisecondsPerSecond),
|
|
UnitPrice: 1000,
|
|
},
|
|
wm,
|
|
&actions.Query{},
|
|
)
|
|
msg, err := tx.Digest()
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
auth, err := factory.Sign(msg, tx.Action)
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
tx.Auth = auth
|
|
p := codec.NewWriter(0, consts.MaxInt)
|
|
gomega.Ω(tx.Marshal(p)).Should(gomega.BeNil())
|
|
gomega.Ω(p.Err()).Should(gomega.BeNil())
|
|
txID, err := instances[0].cli.SubmitTx(
|
|
context.Background(),
|
|
p.Bytes(),
|
|
)
|
|
fmt.Fprintf(ginkgo.GinkgoWriter, "transactionid: %s\n", txID.String())
|
|
gomega.Ω(err).Should(gomega.BeNil())
|
|
})
|
|
|
|
// ginkgo.By("build block & check query result", func() {
|
|
// accept := expectBlk(instances[0])
|
|
// results := accept()
|
|
// gomega.Ω(results).Should(gomega.HaveLen(1))
|
|
|
|
// wm := results[0].WarpMessage
|
|
// payload := wm.Payload
|
|
// var queryRes actions.QueryResult
|
|
// err := json.Unmarshal(payload, &queryRes)
|
|
// gomega.Ω(err).Should(gomega.BeNil())
|
|
// gomega.Ω(queryRes.EntityType).Should(gomega.Equal(uint64(0)))
|
|
// entityPayload := queryRes.Payload
|
|
// stk, err := oracle.UnmarshalStock(entityPayload)
|
|
// gomega.Ω(err).Should(gomega.BeNil())
|
|
// gomega.Ω(stk.Price).Should(gomega.Equal(uint64(1000)))
|
|
// gomega.Ω(stk.Ticker).Should(gomega.Equal("AMD"))
|
|
// })
|
|
})
|
|
})
|
|
|
|
func expectBlk(i instance) func() []*chain.Result {
|
|
ctx := context.TODO()
|
|
|
|
// manually signal ready
|
|
i.vm.Builder().ForceNotify()
|
|
// manually ack ready sig as in engine
|
|
<-i.toEngine
|
|
|
|
blk, err := i.vm.BuildBlock(ctx)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
gomega.Ω(err).To(gomega.BeNil())
|
|
gomega.Ω(blk).To(gomega.Not(gomega.BeNil()))
|
|
|
|
gomega.Ω(blk.Verify(ctx)).To(gomega.BeNil())
|
|
gomega.Ω(blk.Status()).To(gomega.Equal(choices.Processing))
|
|
|
|
err = i.vm.SetPreference(ctx, blk.ID())
|
|
gomega.Ω(err).To(gomega.BeNil())
|
|
|
|
return func() []*chain.Result {
|
|
gomega.Ω(blk.Accept(ctx)).To(gomega.BeNil())
|
|
gomega.Ω(blk.Status()).To(gomega.Equal(choices.Accepted))
|
|
|
|
lastAccepted, err := i.vm.LastAccepted(ctx)
|
|
gomega.Ω(err).To(gomega.BeNil())
|
|
gomega.Ω(lastAccepted).To(gomega.Equal(blk.ID()))
|
|
return blk.(*chain.StatelessBlock).Results()
|
|
}
|
|
}
|
|
|
|
var _ common.AppSender = &appSender{}
|
|
|
|
type appSender struct {
|
|
next int
|
|
instances []instance
|
|
}
|
|
|
|
func (app *appSender) SendAppGossip(ctx context.Context, appGossipBytes []byte) error {
|
|
n := len(app.instances)
|
|
sender := app.instances[app.next].nodeID
|
|
app.next++
|
|
app.next %= n
|
|
return app.instances[app.next].vm.AppGossip(ctx, sender, appGossipBytes)
|
|
}
|
|
|
|
func (*appSender) SendAppRequest(context.Context, set.Set[ids.NodeID], uint32, []byte) error {
|
|
return nil
|
|
}
|
|
|
|
func (*appSender) SendAppResponse(context.Context, ids.NodeID, uint32, []byte) error {
|
|
return nil
|
|
}
|
|
|
|
func (*appSender) SendAppGossipSpecific(context.Context, set.Set[ids.NodeID], []byte) error {
|
|
return nil
|
|
}
|
|
|
|
func (*appSender) SendCrossChainAppRequest(context.Context, ids.ID, uint32, []byte) error {
|
|
return nil
|
|
}
|
|
|
|
func (*appSender) SendCrossChainAppResponse(context.Context, ids.ID, uint32, []byte) error {
|
|
return nil
|
|
}
|