support SendTx to other chains via IBC
This commit is contained in:
parent
38716d4815
commit
412c2b5bb7
|
@ -6,6 +6,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
abci "github.com/tendermint/abci/types"
|
abci "github.com/tendermint/abci/types"
|
||||||
|
@ -13,7 +14,6 @@ import (
|
||||||
merkle "github.com/tendermint/merkleeyes/iavl"
|
merkle "github.com/tendermint/merkleeyes/iavl"
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
|
|
||||||
bcsm "github.com/tendermint/basecoin/state"
|
|
||||||
"github.com/tendermint/basecoin/types"
|
"github.com/tendermint/basecoin/types"
|
||||||
tm "github.com/tendermint/tendermint/types"
|
tm "github.com/tendermint/tendermint/types"
|
||||||
)
|
)
|
||||||
|
@ -55,20 +55,53 @@ type Packet struct {
|
||||||
SrcChainID string
|
SrcChainID string
|
||||||
DstChainID string
|
DstChainID string
|
||||||
Sequence uint64
|
Sequence uint64
|
||||||
Type string
|
Type string // redundant now that Type() is a method on Payload ?
|
||||||
Payload Payload
|
Payload Payload
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPacket(src, dst string, seq uint64, ty string, payload Payload) Packet {
|
func NewPacket(src, dst string, seq uint64, payload Payload) Packet {
|
||||||
return Packet{
|
return Packet{
|
||||||
SrcChainID: src,
|
SrcChainID: src,
|
||||||
DstChainID: dst,
|
DstChainID: dst,
|
||||||
Sequence: seq,
|
Sequence: seq,
|
||||||
Type: ty,
|
Type: payload.Type(),
|
||||||
Payload: payload,
|
Payload: payload,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSequenceNumber gets the sequence number for packets being sent from the src chain to the dst chain
|
||||||
|
func GetSequenceNumber(store types.KVStore, src, dst string) uint64 {
|
||||||
|
sequenceKey := toKey(_IBC, _EGRESS, src, dst)
|
||||||
|
seqBytes := store.Get(sequenceKey)
|
||||||
|
if seqBytes == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
seq, err := strconv.ParseUint(string(seqBytes), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
cmn.PanicSanity(err.Error())
|
||||||
|
}
|
||||||
|
return seq
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSequenceNumber sets the sequence number for packets being sent from the src chain to the dst chain
|
||||||
|
func SetSequenceNumber(store types.KVStore, src, dst string, seq uint64) {
|
||||||
|
sequenceKey := toKey(_IBC, _EGRESS, src, dst)
|
||||||
|
store.Set(sequenceKey, []byte(strconv.FormatUint(seq, 10)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveNewIBCPacket creates an IBC packet with the given payload from the src chain to the dst chain
|
||||||
|
// using the correct sequence number. It also increments the sequence number by 1
|
||||||
|
func SaveNewIBCPacket(state types.KVStore, src, dst string, payload Payload) {
|
||||||
|
// fetch sequence number and increment by 1
|
||||||
|
seq := GetSequenceNumber(state, src, dst)
|
||||||
|
SetSequenceNumber(state, src, dst, seq+1)
|
||||||
|
|
||||||
|
// save ibc packet
|
||||||
|
packetKey := toKey(_IBC, _EGRESS, src, dst, cmn.Fmt("%v", seq))
|
||||||
|
packet := NewPacket(src, dst, uint64(seq), payload)
|
||||||
|
save(state, packetKey, packet)
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -78,21 +111,26 @@ const (
|
||||||
|
|
||||||
var _ = wire.RegisterInterface(
|
var _ = wire.RegisterInterface(
|
||||||
struct{ Payload }{},
|
struct{ Payload }{},
|
||||||
wire.ConcreteType{BytesPayload{}, PayloadTypeBytes},
|
wire.ConcreteType{DataPayload{}, PayloadTypeBytes},
|
||||||
wire.ConcreteType{CoinsPayload{}, PayloadTypeCoins},
|
wire.ConcreteType{CoinsPayload{}, PayloadTypeCoins},
|
||||||
)
|
)
|
||||||
|
|
||||||
type Payload interface {
|
type Payload interface {
|
||||||
AssertIsPayload()
|
AssertIsPayload()
|
||||||
|
Type() string
|
||||||
ValidateBasic() abci.Result
|
ValidateBasic() abci.Result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (BytesPayload) AssertIsPayload() {}
|
func (DataPayload) AssertIsPayload() {}
|
||||||
func (CoinsPayload) AssertIsPayload() {}
|
func (CoinsPayload) AssertIsPayload() {}
|
||||||
|
|
||||||
type BytesPayload []byte
|
type DataPayload []byte
|
||||||
|
|
||||||
func (p BytesPayload) ValidateBasic() abci.Result {
|
func (p DataPayload) Type() string {
|
||||||
|
return "data"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p DataPayload) ValidateBasic() abci.Result {
|
||||||
return abci.OK
|
return abci.OK
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,6 +139,10 @@ type CoinsPayload struct {
|
||||||
Coins types.Coins
|
Coins types.Coins
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p CoinsPayload) Type() string {
|
||||||
|
return "coin"
|
||||||
|
}
|
||||||
|
|
||||||
func (p CoinsPayload) ValidateBasic() abci.Result {
|
func (p CoinsPayload) ValidateBasic() abci.Result {
|
||||||
// TODO: validate
|
// TODO: validate
|
||||||
return abci.OK
|
return abci.OK
|
||||||
|
@ -351,7 +393,7 @@ func (sm *IBCStateMachine) runPacketCreateTx(tx IBCPacketCreateTx) {
|
||||||
|
|
||||||
// Execute the payload
|
// Execute the payload
|
||||||
switch payload := tx.Packet.Payload.(type) {
|
switch payload := tx.Packet.Payload.(type) {
|
||||||
case BytesPayload:
|
case DataPayload:
|
||||||
// do nothing
|
// do nothing
|
||||||
case CoinsPayload:
|
case CoinsPayload:
|
||||||
// ensure enough coins were sent in tx to cover the payload coins
|
// ensure enough coins were sent in tx to cover the payload coins
|
||||||
|
@ -428,16 +470,16 @@ func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) {
|
||||||
|
|
||||||
// Execute payload
|
// Execute payload
|
||||||
switch payload := packet.Payload.(type) {
|
switch payload := packet.Payload.(type) {
|
||||||
case BytesPayload:
|
case DataPayload:
|
||||||
// do nothing
|
// do nothing
|
||||||
case CoinsPayload:
|
case CoinsPayload:
|
||||||
// Add coins to destination account
|
// Add coins to destination account
|
||||||
acc := bcsm.GetAccount(sm.store, payload.Address)
|
acc := types.GetAccount(sm.store, payload.Address)
|
||||||
if acc == nil {
|
if acc == nil {
|
||||||
acc = &types.Account{}
|
acc = &types.Account{}
|
||||||
}
|
}
|
||||||
acc.Balance = acc.Balance.Plus(payload.Coins)
|
acc.Balance = acc.Balance.Plus(payload.Coins)
|
||||||
bcsm.SetAccount(sm.store, payload.Address, acc)
|
types.SetAccount(sm.store, payload.Address, acc)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
@ -17,7 +17,6 @@ import (
|
||||||
"github.com/tendermint/merkleeyes/iavl"
|
"github.com/tendermint/merkleeyes/iavl"
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
|
|
||||||
sm "github.com/tendermint/basecoin/state"
|
|
||||||
"github.com/tendermint/basecoin/types"
|
"github.com/tendermint/basecoin/types"
|
||||||
tm "github.com/tendermint/tendermint/types"
|
tm "github.com/tendermint/tendermint/types"
|
||||||
)
|
)
|
||||||
|
@ -170,7 +169,7 @@ func TestIBCPluginPost(t *testing.T) {
|
||||||
registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1))
|
registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1))
|
||||||
|
|
||||||
// Create a new packet (for testing)
|
// Create a new packet (for testing)
|
||||||
packet := NewPacket("test_chain", "dst_chain", 0, "data", BytesPayload([]byte("hello world")))
|
packet := NewPacket("test_chain", "dst_chain", 0, DataPayload([]byte("hello world")))
|
||||||
res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
|
res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
|
||||||
Packet: packet,
|
Packet: packet,
|
||||||
}}))
|
}}))
|
||||||
|
@ -203,7 +202,7 @@ func TestIBCPluginPayloadBytes(t *testing.T) {
|
||||||
registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1))
|
registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1))
|
||||||
|
|
||||||
// Create a new packet (for testing)
|
// Create a new packet (for testing)
|
||||||
packet := NewPacket("test_chain", "dst_chain", 0, "data", BytesPayload([]byte("hello world")))
|
packet := NewPacket("test_chain", "dst_chain", 0, DataPayload([]byte("hello world")))
|
||||||
res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
|
res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
|
||||||
Packet: packet,
|
Packet: packet,
|
||||||
}}))
|
}}))
|
||||||
|
@ -282,7 +281,7 @@ func TestIBCPluginPayloadCoins(t *testing.T) {
|
||||||
coinsGood := types.Coins{types.Coin{"mycoin", 1}}
|
coinsGood := types.Coins{types.Coin{"mycoin", 1}}
|
||||||
|
|
||||||
// Try to send too many coins
|
// Try to send too many coins
|
||||||
packet := NewPacket("test_chain", "dst_chain", 0, "data", CoinsPayload{
|
packet := NewPacket("test_chain", "dst_chain", 0, CoinsPayload{
|
||||||
Address: destinationAddr,
|
Address: destinationAddr,
|
||||||
Coins: coinsBad,
|
Coins: coinsBad,
|
||||||
})
|
})
|
||||||
|
@ -292,7 +291,7 @@ func TestIBCPluginPayloadCoins(t *testing.T) {
|
||||||
assertAndLog(t, store, res, abci.CodeType_InsufficientFunds)
|
assertAndLog(t, store, res, abci.CodeType_InsufficientFunds)
|
||||||
|
|
||||||
// Send a small enough number of coins
|
// Send a small enough number of coins
|
||||||
packet = NewPacket("test_chain", "dst_chain", 0, "data", CoinsPayload{
|
packet = NewPacket("test_chain", "dst_chain", 0, CoinsPayload{
|
||||||
Address: destinationAddr,
|
Address: destinationAddr,
|
||||||
Coins: coinsGood,
|
Coins: coinsGood,
|
||||||
})
|
})
|
||||||
|
@ -334,7 +333,7 @@ func TestIBCPluginPayloadCoins(t *testing.T) {
|
||||||
assert.Nil(err)
|
assert.Nil(err)
|
||||||
|
|
||||||
// Account should be empty before the tx
|
// Account should be empty before the tx
|
||||||
acc := sm.GetAccount(store, destinationAddr)
|
acc := types.GetAccount(store, destinationAddr)
|
||||||
assert.Nil(acc)
|
assert.Nil(acc)
|
||||||
|
|
||||||
// Post a packet
|
// Post a packet
|
||||||
|
@ -347,7 +346,7 @@ func TestIBCPluginPayloadCoins(t *testing.T) {
|
||||||
assertAndLog(t, store, res, abci.CodeType_OK)
|
assertAndLog(t, store, res, abci.CodeType_OK)
|
||||||
|
|
||||||
// Account should now have some coins
|
// Account should now have some coins
|
||||||
acc = sm.GetAccount(store, destinationAddr)
|
acc = types.GetAccount(store, destinationAddr)
|
||||||
assert.Equal(acc.Balance, coinsGood)
|
assert.Equal(acc.Balance, coinsGood)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -408,7 +407,7 @@ func TestIBCPluginBadProof(t *testing.T) {
|
||||||
registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1))
|
registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1))
|
||||||
|
|
||||||
// Create a new packet (for testing)
|
// Create a new packet (for testing)
|
||||||
packet := NewPacket("test_chain", "dst_chain", 0, "data", BytesPayload([]byte("hello world")))
|
packet := NewPacket("test_chain", "dst_chain", 0, DataPayload([]byte("hello world")))
|
||||||
res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
|
res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
|
||||||
Packet: packet,
|
Packet: packet,
|
||||||
}}))
|
}}))
|
||||||
|
|
|
@ -2,9 +2,11 @@ package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
abci "github.com/tendermint/abci/types"
|
abci "github.com/tendermint/abci/types"
|
||||||
"github.com/tendermint/basecoin/types"
|
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
"github.com/tendermint/tmlibs/events"
|
"github.com/tendermint/tmlibs/events"
|
||||||
|
|
||||||
|
"github.com/tendermint/basecoin/plugins/ibc"
|
||||||
|
"github.com/tendermint/basecoin/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// If the tx is invalid, a TMSP error will be returned.
|
// If the tx is invalid, a TMSP error will be returned.
|
||||||
|
@ -189,17 +191,23 @@ func getOrMakeOutputs(state types.AccountGetter, accounts map[string]*types.Acco
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, out := range outs {
|
for _, out := range outs {
|
||||||
|
chain, outAddress, _ := out.ChainAndAddress() // already validated
|
||||||
|
if chain != nil {
|
||||||
|
// we dont need an account for the other chain.
|
||||||
|
// we'll just create an outgoing ibc packet
|
||||||
|
continue
|
||||||
|
}
|
||||||
// Account shouldn't be duplicated
|
// Account shouldn't be duplicated
|
||||||
if _, ok := accounts[string(out.Address)]; ok {
|
if _, ok := accounts[string(outAddress)]; ok {
|
||||||
return nil, abci.ErrBaseDuplicateAddress
|
return nil, abci.ErrBaseDuplicateAddress
|
||||||
}
|
}
|
||||||
acc := state.GetAccount(out.Address)
|
acc := state.GetAccount(outAddress)
|
||||||
// output account may be nil (new)
|
// output account may be nil (new)
|
||||||
if acc == nil {
|
if acc == nil {
|
||||||
// zero value is valid, empty account
|
// zero value is valid, empty account
|
||||||
acc = &types.Account{}
|
acc = &types.Account{}
|
||||||
}
|
}
|
||||||
accounts[string(out.Address)] = acc
|
accounts[string(outAddress)] = acc
|
||||||
}
|
}
|
||||||
return accounts, abci.OK
|
return accounts, abci.OK
|
||||||
}
|
}
|
||||||
|
@ -281,15 +289,22 @@ func adjustByInputs(state types.AccountSetter, accounts map[string]*types.Accoun
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func adjustByOutputs(state types.AccountSetter, accounts map[string]*types.Account, outs []types.TxOutput, isCheckTx bool) {
|
func adjustByOutputs(state *State, accounts map[string]*types.Account, outs []types.TxOutput, isCheckTx bool) {
|
||||||
for _, out := range outs {
|
for _, out := range outs {
|
||||||
acc := accounts[string(out.Address)]
|
destChain, outAddress, _ := out.ChainAndAddress() // already validated
|
||||||
|
if destChain != nil {
|
||||||
|
payload := ibc.CoinsPayload{outAddress, out.Coins}
|
||||||
|
ibc.SaveNewIBCPacket(state, state.GetChainID(), string(destChain), payload)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
acc := accounts[string(outAddress)]
|
||||||
if acc == nil {
|
if acc == nil {
|
||||||
cmn.PanicSanity("adjustByOutputs() expects account in accounts")
|
cmn.PanicSanity("adjustByOutputs() expects account in accounts")
|
||||||
}
|
}
|
||||||
acc.Balance = acc.Balance.Plus(out.Coins)
|
acc.Balance = acc.Balance.Plus(out.Coins)
|
||||||
if !isCheckTx {
|
if !isCheckTx {
|
||||||
state.SetAccount(out.Address, acc)
|
state.SetAccount(outAddress, acc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,7 @@ package state
|
||||||
import (
|
import (
|
||||||
abci "github.com/tendermint/abci/types"
|
abci "github.com/tendermint/abci/types"
|
||||||
"github.com/tendermint/basecoin/types"
|
"github.com/tendermint/basecoin/types"
|
||||||
wire "github.com/tendermint/go-wire"
|
|
||||||
eyes "github.com/tendermint/merkleeyes/client"
|
eyes "github.com/tendermint/merkleeyes/client"
|
||||||
. "github.com/tendermint/tmlibs/common"
|
|
||||||
"github.com/tendermint/tmlibs/log"
|
"github.com/tendermint/tmlibs/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -64,11 +62,11 @@ func (s *State) Set(key []byte, value []byte) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) GetAccount(addr []byte) *types.Account {
|
func (s *State) GetAccount(addr []byte) *types.Account {
|
||||||
return GetAccount(s, addr)
|
return types.GetAccount(s, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) SetAccount(addr []byte, acc *types.Account) {
|
func (s *State) SetAccount(addr []byte, acc *types.Account) {
|
||||||
SetAccount(s, addr, acc)
|
types.SetAccount(s, addr, acc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) CacheWrap() *State {
|
func (s *State) CacheWrap() *State {
|
||||||
|
@ -97,28 +95,3 @@ func (s *State) Commit() abci.Result {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------
|
|
||||||
|
|
||||||
func AccountKey(addr []byte) []byte {
|
|
||||||
return append([]byte("base/a/"), addr...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetAccount(store types.KVStore, addr []byte) *types.Account {
|
|
||||||
data := store.Get(AccountKey(addr))
|
|
||||||
if len(data) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var acc *types.Account
|
|
||||||
err := wire.ReadBinaryBytes(data, &acc)
|
|
||||||
if err != nil {
|
|
||||||
panic(Fmt("Error reading account %X error: %v",
|
|
||||||
data, err.Error()))
|
|
||||||
}
|
|
||||||
return acc
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetAccount(store types.KVStore, addr []byte, acc *types.Account) {
|
|
||||||
accBytes := wire.BinaryBytes(acc)
|
|
||||||
store.Set(AccountKey(addr), accBytes)
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/tendermint/go-crypto"
|
"github.com/tendermint/go-crypto"
|
||||||
|
"github.com/tendermint/go-wire"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Account struct {
|
type Account struct {
|
||||||
|
@ -49,3 +50,26 @@ type AccountGetterSetter interface {
|
||||||
GetAccount(addr []byte) *Account
|
GetAccount(addr []byte) *Account
|
||||||
SetAccount(addr []byte, acc *Account)
|
SetAccount(addr []byte, acc *Account)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AccountKey(addr []byte) []byte {
|
||||||
|
return append([]byte("base/a/"), addr...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAccount(store KVStore, addr []byte) *Account {
|
||||||
|
data := store.Get(AccountKey(addr))
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var acc *Account
|
||||||
|
err := wire.ReadBinaryBytes(data, &acc)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("Error reading account %X error: %v",
|
||||||
|
data, err.Error()))
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetAccount(store KVStore, addr []byte, acc *Account) {
|
||||||
|
accBytes := wire.BinaryBytes(acc)
|
||||||
|
store.Set(AccountKey(addr), accBytes)
|
||||||
|
}
|
||||||
|
|
29
types/tx.go
29
types/tx.go
|
@ -116,10 +116,33 @@ type TxOutput struct {
|
||||||
Coins Coins `json:"coins"` //
|
Coins Coins `json:"coins"` //
|
||||||
}
|
}
|
||||||
|
|
||||||
func (txOut TxOutput) ValidateBasic() abci.Result {
|
// An output destined for another chain may be formatted as `chainID/address`.
|
||||||
if len(txOut.Address) != 20 {
|
// ChainAndAddress returns the chainID prefix and the address.
|
||||||
return abci.ErrBaseInvalidOutput.AppendLog("Invalid address length")
|
// If there is no chainID prefix, the first returned value is nil.
|
||||||
|
func (txOut TxOutput) ChainAndAddress() ([]byte, []byte, abci.Result) {
|
||||||
|
var chainPrefix []byte
|
||||||
|
address := txOut.Address
|
||||||
|
if len(address) > 20 {
|
||||||
|
spl := bytes.Split(address, []byte("/"))
|
||||||
|
if len(spl) < 2 {
|
||||||
|
return nil, nil, abci.ErrBaseInvalidOutput.AppendLog("Invalid address format")
|
||||||
}
|
}
|
||||||
|
chainPrefix = spl[0]
|
||||||
|
address = bytes.Join(spl[1:], nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(address) != 20 {
|
||||||
|
return nil, nil, abci.ErrBaseInvalidOutput.AppendLog("Invalid address length")
|
||||||
|
}
|
||||||
|
return chainPrefix, address, abci.OK
|
||||||
|
}
|
||||||
|
|
||||||
|
func (txOut TxOutput) ValidateBasic() abci.Result {
|
||||||
|
_, _, r := txOut.ChainAndAddress()
|
||||||
|
if r.IsErr() {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
if !txOut.Coins.IsValid() {
|
if !txOut.Coins.IsValid() {
|
||||||
return abci.ErrBaseInvalidOutput.AppendLog(Fmt("Invalid coins %v", txOut.Coins))
|
return abci.ErrBaseInvalidOutput.AppendLog(Fmt("Invalid coins %v", txOut.Coins))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue